UNPKG

locutus

Version:

Locutus other languages' standard libraries to JavaScript for fun and educational purposes

270 lines (269 loc) 10.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.toCalendarInteger = toCalendarInteger; exports.normalizeCalendarYear = normalizeCalendarYear; exports.normalizeCalendarMonth = normalizeCalendarMonth; exports.normalizeCalendarDay = normalizeCalendarDay; exports.normalizeWeekWidth = normalizeWeekWidth; exports.normalizeFormatColumnWidth = normalizeFormatColumnWidth; exports.normalizeFormatSpacing = normalizeFormatSpacing; exports.normalizeMonthWidth = normalizeMonthWidth; exports.normalizeLineSpacing = normalizeLineSpacing; exports.normalizeColumnSpacing = normalizeColumnSpacing; exports.normalizeMonthsPerRow = normalizeMonthsPerRow; exports.normalizeTimeTuple = normalizeTimeTuple; exports.isLeapYear = isLeapYear; exports.daysInMonth = daysInMonth; exports.pythonWeekday = pythonWeekday; exports.countLeapYearsBetween = countLeapYearsBetween; exports.buildWeekHeader = buildWeekHeader; exports.buildMonthCalendar = buildMonthCalendar; exports.formatWeek = formatWeek; exports.formatString = formatString; exports.formatMonth = formatMonth; exports.formatYear = formatYear; exports.timeTupleToUnixSeconds = timeTupleToUnixSeconds; const DAY_ABBR = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; const MONTH_NAMES = [ '', 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December', ]; function toCalendarInteger(value, functionName) { if (typeof value === 'boolean') { return value ? 1 : 0; } if (typeof value === 'bigint') { const numericValue = Number(value); if (!Number.isSafeInteger(numericValue)) { throw new RangeError(`${functionName}() integer arguments must fit within JS safe integer precision`); } return numericValue; } if (typeof value === 'number' && Number.isFinite(value) && Number.isSafeInteger(value)) { return value; } throw new TypeError(`${functionName}() requires integer arguments`); } function normalizeCalendarYear(value, functionName) { return toCalendarInteger(value, functionName); } function normalizeCalendarMonth(value, functionName) { const month = toCalendarInteger(value, functionName); if (month < 1 || month > 12) { throw new RangeError('bad month number'); } return month; } function normalizeCalendarDay(year, month, value, functionName) { const day = toCalendarInteger(value, functionName); const maxDay = daysInMonth(year, month); if (day < 1 || day > maxDay) { throw new RangeError('day is out of range for month'); } return day; } function normalizeWeekWidth(value, functionName) { return toCalendarInteger(value, functionName); } function normalizeFormatColumnWidth(value, functionName) { return toCalendarInteger(value, functionName); } function normalizeFormatSpacing(value, functionName) { return toCalendarInteger(value, functionName); } function normalizeMonthWidth(value, functionName) { return Math.max(toCalendarInteger(value, functionName), 2); } function normalizeLineSpacing(value, functionName) { return Math.max(toCalendarInteger(value, functionName), 1); } function normalizeColumnSpacing(value, functionName) { return Math.max(toCalendarInteger(value, functionName), 2); } function normalizeMonthsPerRow(value, functionName) { return Math.max(toCalendarInteger(value, functionName), 1); } function normalizeTimeTuple(value, functionName) { if (!Array.isArray(value) || value.length < 6) { throw new ValueError(`not enough values to unpack (expected 6, got ${Array.isArray(value) ? value.length : 0})`); } const [yearRaw, monthRaw, dayRaw, hourRaw, minuteRaw, secondRaw] = value; const year = normalizeCalendarYear(yearRaw, functionName); const month = normalizeCalendarMonth(monthRaw, functionName); const day = toCalendarInteger(dayRaw, functionName); const hour = toCalendarInteger(hourRaw, functionName); const minute = toCalendarInteger(minuteRaw, functionName); const second = toCalendarInteger(secondRaw, functionName); return [year, month, day, hour, minute, second]; } function isLeapYear(year) { return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0); } function daysInMonth(year, month) { if (month === 2) { return isLeapYear(year) ? 29 : 28; } return [4, 6, 9, 11].includes(month) ? 30 : 31; } function pythonWeekday(year, month, day) { const ordinal = daysBeforeYear(year) + daysBeforeMonth(year, month) + day; return positiveModulo(ordinal - 1, 7); } function countLeapYearsBetween(startYear, endYear) { return countLeapYearsBefore(endYear) - countLeapYearsBefore(startYear); } function buildWeekHeader(width) { return DAY_ABBR.map((name) => { const truncated = name.slice(0, width); return width > truncated.length ? centerText(truncated, width) : truncated; }).join(' '); } function buildMonthCalendar(year, month) { const totalDays = daysInMonth(year, month); const firstWeekday = pythonWeekday(year, month, 1); const weeks = []; let currentWeek = new Array(7).fill(0); let weekdayIndex = firstWeekday; for (let day = 1; day <= totalDays; day += 1) { currentWeek[weekdayIndex] = day; weekdayIndex += 1; if (weekdayIndex === 7) { weeks.push(currentWeek); currentWeek = new Array(7).fill(0); weekdayIndex = 0; } } if (weekdayIndex !== 0) { weeks.push(currentWeek); } return weeks; } function formatWeek(theweek, width) { if (!Array.isArray(theweek)) { throw new TypeError("'int' object is not iterable"); } const rawWidth = toCalendarInteger(width, 'week'); return theweek .map((entry) => { if (!Array.isArray(entry)) { throw new TypeError("'int' object is not iterable"); } const day = entry[0]; if (typeof day !== 'number' || !Number.isInteger(day)) { throw new TypeError("'int' object is not iterable"); } if (day === 0) { return centerText('', rawWidth); } return centerText(String(day).padStart(2, ' '), rawWidth); }) .join(' '); } function formatString(items, cols = 20, spacing = 6) { if (!isIterable(items)) { throw new TypeError("'object' object is not iterable"); } const values = [...items]; return values.map((value) => centerMethodString(value, cols)).join(' '.repeat(spacing)); } function formatMonth(year, month, width, lineSpacing) { const monthWidth = 7 * (width + 1) - 1; const header = centerText(`${MONTH_NAMES[month]} ${year}`, monthWidth); const weekHeader = buildWeekHeader(width); const weeks = formatMonthWeeks(year, month, width); return joinCalendarLines([header, weekHeader, ...weeks], lineSpacing); } function formatYear(year, width, lineSpacing, columnSpacing, monthsPerRow) { const monthWidth = 7 * (width + 1) - 1; const yearWidth = monthWidth * monthsPerRow + columnSpacing * (monthsPerRow - 1); const lines = [centerText(String(year), yearWidth).replace(/\s+$/, ''), '\n'.repeat(lineSpacing)]; const header = buildWeekHeader(width); const monthRows = Array.from({ length: Math.ceil(12 / monthsPerRow) }, (_, rowIndex) => Array.from({ length: monthsPerRow }, (_, columnIndex) => rowIndex * monthsPerRow + columnIndex + 1).filter((month) => month <= 12)); for (const months of monthRows) { lines.push('\n'.repeat(lineSpacing)); lines.push(formatString(months.map((month) => centerText(MONTH_NAMES[month] ?? '', monthWidth)), monthWidth, columnSpacing).replace(/\s+$/, '')); lines.push('\n'.repeat(lineSpacing)); lines.push(formatString(months.map(() => header), monthWidth, columnSpacing).replace(/\s+$/, '')); lines.push('\n'.repeat(lineSpacing)); const weeks = months.map((month) => buildMonthCalendar(year, month)); const height = Math.max(...weeks.map((calendarRows) => calendarRows.length)); for (let weekIndex = 0; weekIndex < height; weekIndex += 1) { lines.push(formatString(weeks.map((calendarRows) => weekIndex >= calendarRows.length ? '' : formatWeek((calendarRows[weekIndex] ?? []).map((day, weekday) => [day, weekday]), width)), monthWidth, columnSpacing).replace(/\s+$/, '')); lines.push('\n'.repeat(lineSpacing)); } } return lines.join(''); } function countLeapYearsBefore(yearExclusive) { const adjusted = yearExclusive - 1; return Math.floor(adjusted / 4) - Math.floor(adjusted / 100) + Math.floor(adjusted / 400); } function timeTupleToUnixSeconds(year, month, day, hour, minute, second) { const epochOrdinal = daysBeforeYear(1970) + 1; const ordinal = daysBeforeYear(year) + daysBeforeMonth(year, month) + day; const daysSinceEpoch = ordinal - epochOrdinal; return daysSinceEpoch * 86400 + hour * 3600 + minute * 60 + second; } function daysBeforeYear(year) { const adjusted = year - 1; return adjusted * 365 + Math.floor(adjusted / 4) - Math.floor(adjusted / 100) + Math.floor(adjusted / 400); } function daysBeforeMonth(year, month) { const offsets = isLeapYear(year) ? [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335] : [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]; return offsets[month - 1] ?? 0; } function centerText(value, width) { if (width <= value.length) { return value; } const leftPadding = Math.floor((width - value.length) / 2); const rightPadding = width - value.length - leftPadding; return `${' '.repeat(leftPadding)}${value}${' '.repeat(rightPadding)}`; } function positiveModulo(value, modulus) { return ((value % modulus) + modulus) % modulus; } function isIterable(value) { return (value !== null && value !== undefined && typeof value[Symbol.iterator] === 'function'); } function centerMethodString(value, width) { if (typeof value !== 'string') { throw new AttributeError(`${value === null ? 'NoneType' : typeof value} object has no attribute 'center'`); } return centerText(value, width); } function joinCalendarLines(lines, lineSpacing) { return `${lines.map((line) => line.replace(/\s+$/, '')).join('\n'.repeat(lineSpacing))}\n`; } function formatMonthWeeks(year, month, width) { return buildMonthCalendar(year, month).map((week) => formatWeek(week.map((day, weekday) => [day, weekday]), width)); } class ValueError extends Error { constructor(message) { super(message); this.name = 'ValueError'; } } class AttributeError extends Error { constructor(message) { super(message); this.name = 'AttributeError'; } }