UNPKG

@accounter/server

Version:
237 lines (217 loc) • 6.55 kB
import { format } from 'date-fns'; import { UUID_REGEX } from '../constants.js'; import type { TimelessDateString } from '../types/index.js'; function parseIntRound(v: number) { return Math.trunc(v + Math.sign(v) / 2); } export function stringNumberRounded(number: string): number { return parseIntRound((parseFloat(number) + Number.EPSILON) * 100) / 100; } export function numberRounded(number: number): number { return parseIntRound((number + Number.EPSILON) * 100) / 100; } export function isTimelessDateString(date: string): date is TimelessDateString { const parts = date.split('-'); if (parts.length !== 3) { return false; } const [year, month, day] = parts; //year const yearNum = Number(year); if (Number.isNaN(yearNum) || yearNum < 2000 || yearNum > 2049) { return false; } // month const monthNum = Number(month); if (Number.isNaN(monthNum) || monthNum < 1 || monthNum > 12) { return false; } // day const dayNum = Number(day); if (Number.isNaN(dayNum) || dayNum < 1 || dayNum > 31) { return false; } return true; } export function isUUID(raw: string) { return UUID_REGEX.test(raw); } function convertMonthNameToNumber(monthName: string): string | null { const lowerCasedName = monthName.toLocaleLowerCase(); let month: string | null = null; switch (lowerCasedName) { case 'january': case 'jan': month = '01'; break; case 'february': case 'feb': month = '02'; break; case 'march': case 'mar': month = '03'; break; case 'april': case 'apr': month = '04'; break; case 'may': month = '05'; break; case 'june': case 'jun': month = '06'; break; case 'july': case 'jul': month = '07'; break; case 'august': case 'aug': month = '08'; break; case 'september': case 'sep': month = '09'; break; case 'october': case 'oct': month = '10'; break; case 'november': case 'nov': month = '11'; break; case 'december': case 'dec': month = '12'; break; default: break; } return month; } /** * @description * Extract month from description * @param rawDescription string - description to extract month from * @param eventDate Date - optional, if provided, will use it to determine year * @returns month in format yyyy-mm, else null */ export function getMonthFromDescription(rawDescription: string, eventDate?: Date): string[] | null { if (!rawDescription.length) { return null; } const description = rawDescription?.toLocaleLowerCase(); // search for "yyyy-mm" in description const dateRegex = /\b(\d{4})-(\d{2})\b/; const matches = description.match(dateRegex); if (matches?.length) { const month = matches[0]; return [month]; } // search for "mm-yyyy" in description const dateRegex2 = /\b(\d{2})-(\d{4})\b/; const matches2 = description.match(dateRegex2); if (matches2?.length) { const month = matches2[0]; const adjustedMonth = month.split('-').reverse().join('-'); return [adjustedMonth]; } // search for "mm/yyyy" in description const dateRegex3 = /\b(\d{2})\/(\d{4})\b/; const matches3 = description.match(dateRegex3); if (matches3?.length) { // case two month const dateRegex3double = /\b(\d{2})-(\d{2})\/(\d{4})\b/; const matches3double = description.match(dateRegex3double); if (matches3double?.length) { const [_dateString, monthA, monthB, year] = matches3double; return [`${year}-${monthA}`, `${year}-${monthB}`]; } // case one month const month = matches3[0]; const adjustedMonth = month.split('/').reverse().join('-'); return [adjustedMonth]; } // search for "mm/yy" in description const dateRegex4 = /\b(\d{2})\/(\d{2})\b/; const matches4 = description.match(dateRegex4); if (matches4?.length) { const month = matches4[0]; const adjustedMonth = '20' + month.split('/').reverse().join('-'); return [adjustedMonth]; } // search for month name in description const dateRegex5 = /\b(?:jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|jul(?:y)?|aug(?:ust)?|sep(?:tember)?|oct(?:ober)?|(nov|dec)(?:ember)?)\b/g; const monthNames = description.match(dateRegex5); if (monthNames?.length) { const dateStrings = []; for (const monthName of monthNames) { const month = convertMonthNameToNumber(monthName); if (month) { // try to search for year in description const dateRegex5 = /\b(?:jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|jul(?:y)?|aug(?:ust)?|sep(?:tember)?|oct(?:ober)?|(nov|dec)(?:ember)?) (?:19[7-9]\d|2\d{3})\b/g; const matches5 = description.match(dateRegex5); if (matches5?.length) { const year = matches5[0].split(' ')[1]; if (year) { const adjustedMonth = `${year}-${month}`; dateStrings.push(adjustedMonth); continue; } } if (eventDate) { let year = eventDate.getFullYear(); // case date is in Jan/Feb and salary month is Nov/Dec, use date's prev year if (eventDate.getMonth() < 2 && month > '10') { year--; } const adjustedMonth = `${year}-${month}`; dateStrings.push(adjustedMonth); } } } if (dateStrings.length) { return dateStrings; } } return null; } export function dateToTimelessDateString(date: Date): TimelessDateString { return format(date, 'yyyy-MM-dd') as TimelessDateString; } export function optionalDateToTimelessDateString(date?: Date | null): TimelessDateString | null { if (!date) { return null; } return dateToTimelessDateString(date) as TimelessDateString; } // Convert to 32bit integer export function hashStringToInt(text: string): number { let hash = 0; if (text.length === 0) return hash; for (let i = 0; i < text.length; i++) { const chr = text.charCodeAt(i); hash = (hash << 5) - hash + chr; hash |= 0; } return hash; } export function reassureOwnerIdExists< T extends { ownerId?: string | null | void; }, >(params: T, contextOwnerId: string): Omit<T, 'ownerId'> & { ownerId: string } { const ownerId = params.ownerId ?? contextOwnerId; if (!ownerId) { throw new Error('Owner ID is required but could not be determined from parameters or context'); } return { ...params, ownerId, }; }