#[Copyright 2025 ITwrx. This file is part of Forget-Me-Not. Forget-Me-Not is released under the GNU Affero General Public License 3.0. See COPYING or for details.]# import std/[times, strutils] proc weekdayFromString(dayStr: string): Weekday = ## Convert a string representation of a weekday to Weekday enum case dayStr of "Monday": return dMon of "Tuesday": return dTue of "Wednesday": return dWed of "Thursday": return dThu of "Friday": return dFri of "Saturday": return dSat of "Sunday": return dSun proc monthFromString(monthStr: string): Month = ## Convert a string representation of a weekday to Weekday enum case monthStr of "January": return mJan of "Febuary": return mFeb of "March": return mMar of "April": return mApr of "May": return mMay of "June": return mJun of "July": return mJul of "August": return mAug of "September": return mSep of "October": return mOct of "November": return mNov of "December": return mDec proc nextWeekday*(targetWeekdayString: string): DateTime = ## Calculates the next occurrence of a specific weekday var nextDate = now() # We're not including current day, so we move to the next day nextDate = nextDate + 1.days #convert passed weekday string to nim Weekday. let targetWeekday = weekdayFromString(targetWeekdayString) # Find the next occurrence of the target weekday while nextDate.weekday != targetWeekday: nextDate = nextDate + 1.days return nextDate proc nthWeekdayInMonth*(year: int, month: Month, weekdayString: string, nth: range[1..3]): DateTime = # Start from the first day of the month var currentDate = dateTime(year, month, 1) let weekday = weekdayFromString(weekdayString) # Find the first occurrence of the specified weekday while currentDate.weekday != weekday: currentDate = currentDate + 1.days # Move to the nth occurrence currentDate = currentDate + days((nth - 1) * 7) return currentDate proc lastWeekdayInMonth(year: int, month: Month, weekdayString: string): DateTime = # Create a DateTime for the last day of the given month var lastDay = dateTime(year, month, getDaysInMonth(month, year)) let weekday = weekdayFromString(weekdayString) # Work backwards until we find the last occurrence of the specified weekday while lastDay.weekday != weekday: lastDay = lastDay - 1.days return lastDay proc nextMonthlyOnWeekdayOfWeek*(weekdayString: string, ocurrence: string): DateTime = var nextSendDate: DateTime let nextMonthsDate = now() + 1.months let weekNumStrings = @["1", "2", "3"] if ocurrence in weekNumStrings: #nextSendDate = nthWeekdayInMonth(year(nextMonthsDate), month(now()), weekdayString, parseInt(ocurrence)) nextSendDate = nthWeekdayInMonth(year(now()), month(now()), weekdayString, parseInt(ocurrence)) if format(nextSendDate, "yyyy-MM-dd") <= format(now(), "yyyy-MM-dd"): nextSendDate = nthWeekdayInMonth(year(nextMonthsDate), month(nextMonthsDate), weekdayString, parseInt(ocurrence)) else: #nextSendDate = lastWeekdayInMonth(year(nextMonthsDate), month(now()), weekdayString) nextSendDate = lastWeekdayInMonth(year(now()), month(now()), weekdayString) if format(nextSendDate, "yyyy-MM-dd") <= format(now(), "yyyy-MM-dd"): nextSendDate = lastWeekdayInMonth(year(nextMonthsDate), month(nextMonthsDate), weekdayString) return nextSendDate proc nextYearlyOnWeekdayOfWeekOfMonth*(weekdayString, ocurrence, monthString: string): DateTime = var nextSendDate, startingDate, nextYearsDate: DateTime nextYearsDate = now() + 1.years nextYearsDate = dateTime(year(nextYearsDate), monthFromString(monthString), 01) let weekNumStrings = @["1", "2", "3"] if ocurrence in weekNumStrings: nextSendDate = nthWeekdayInMonth(year(now()), month(now()), weekdayString, parseInt(ocurrence)) if format(nextSendDate, "yyyy-MM-dd") <= format(now(), "yyyy-MM-dd"): nextSendDate = nthWeekdayInMonth(year(nextYearsDate), month(nextYearsDate), weekdayString, parseInt(ocurrence)) else: nextSendDate = lastWeekdayInMonth(year(now()), month(now()), weekdayString) if format(nextSendDate, "yyyy-MM-dd") <= format(now(), "yyyy-MM-dd"): nextSendDate = lastWeekdayInMonth(year(nextYearsDate), month(nextYearsDate), weekdayString) return nextSendDate #[ Returns the next occurrence of the specified month and day, which could be: - Later this year if the target date hasn't occurred yet - Next year if the target date has already passed this year]# proc nextYearlyDate*(monthString: string, targetDay: int): DateTime = let baseDate = now() let targetMonth = monthFromString(monthString) # Get the current year let currentYear = baseDate.year # Create a DateTime for the target date in the current year var nextSendDate = dateTime(currentYear, targetMonth, targetDay, 00, 00, 00, 00) # If the target date has already passed this year, move to next year if nextSendDate <= baseDate: nextSendDate = dateTime(currentYear + 1, targetMonth, targetDay, 00, 00, 00, 00) let nextSendDateString = format(nextSendDate, "yyyy-MM-dd") nextSendDate = parse(nextSendDateString, "yyyy-MM-dd") return nextSendDate