/** * Düzenli işlem tekrarı (Bölüm 6.7). Saf fonksiyonlar: tarihler "YYYY-MM-DD" (İstanbul günü). * Aylık/yıllık tekrarda gün, başlangıç tarihinin gününden alınır ve ayda yoksa ayın son günü * kullanılır (31 Ocak → 28/29 Şubat → 31 Mart). "Ayın son günü" modu her zaman son günü verir. */ export const FREQUENCIES = ["daily", "weekly", "monthly", "yearly"] as const; export type Frequency = (typeof FREQUENCIES)[number]; export const MONTHLY_MODES = ["day", "last"] as const; export type MonthlyMode = (typeof MONTHLY_MODES)[number]; /** Planlı kayıtların üretileceği ufuk (ay). */ export const HORIZON_MONTHS = 12; export const MAX_OCCURRENCES = 600; export type Recurrence = { frequency: Frequency; /** Her N gün/hafta/ay/yılda bir (1–12). */ interval: number; monthlyMode: MonthlyMode; startDate: string; endDate: string | null; /** Toplam tekrar sayısı (ör. 12) — endDate ile birlikte verilirse hangisi önce gelirse. */ count: number | null; }; const parse = (ymd: string) => { const [y, m, d] = ymd.split("-").map(Number) as [number, number, number]; return { y, m, d }; }; const fmt = (y: number, m: number, d: number) => `${String(y).padStart(4, "0")}-${String(m).padStart(2, "0")}-${String(d).padStart(2, "0")}`; export function daysInMonth(y: number, m: number): number { return new Date(Date.UTC(y, m, 0)).getUTCDate(); } export function addDays(ymd: string, days: number): string { const { y, m, d } = parse(ymd); const t = new Date(Date.UTC(y, m - 1, d + days)); return fmt(t.getUTCFullYear(), t.getUTCMonth() + 1, t.getUTCDate()); } /** Ay ekler; gün ayda yoksa ayın son günü. `day` verilirse o gün hedeflenir. */ export function addMonths(ymd: string, months: number, day?: number | "last"): string { const { y, m, d } = parse(ymd); const index = y * 12 + (m - 1) + months; const ny = Math.floor(index / 12); const nm = (index % 12) + 1; const last = daysInMonth(ny, nm); const target = day === "last" ? last : Math.min(day ?? d, last); return fmt(ny, nm, target); } /** index. tekrarın tarihi (0 = başlangıç; "son gün" modunda başlangıç ayının son günü). */ export function occurrenceDate(r: Recurrence, index: number): string { const n = index * r.interval; switch (r.frequency) { case "daily": return addDays(r.startDate, n); case "weekly": return addDays(r.startDate, n * 7); case "monthly": return addMonths(r.startDate, n, r.monthlyMode === "last" ? "last" : parse(r.startDate).d); case "yearly": return addMonths(r.startDate, n * 12, parse(r.startDate).d); } } /** Ufuk tarihine (dahil) kadar, `fromIndex`ten başlayarak tekrarlar. */ export function listOccurrences( r: Recurrence, until: string, fromIndex = 0, ): { index: number; date: string }[] { const out: { index: number; date: string }[] = []; for (let i = fromIndex; i < MAX_OCCURRENCES; i++) { if (r.count !== null && i >= r.count) break; const date = occurrenceDate(r, i); if (r.endDate && date > r.endDate) break; if (date > until) break; out.push({ index: i, date }); } return out; } /** Seri bitti mi (bir sonraki tekrar yok)? */ export function isFinished(r: Recurrence, nextIndex: number): boolean { if (r.count !== null && nextIndex >= r.count) return true; return Boolean(r.endDate && occurrenceDate(r, nextIndex) > r.endDate); } export function horizonDate(today: string): string { return addMonths(today, HORIZON_MONTHS); } const MONTHS = [ "Ocak", "Şubat", "Mart", "Nisan", "Mayıs", "Haziran", "Temmuz", "Ağustos", "Eylül", "Ekim", "Kasım", "Aralık", ]; const WEEKDAYS = ["Pazar", "Pazartesi", "Salı", "Çarşamba", "Perşembe", "Cuma", "Cumartesi"]; /** "Her ay 3. gün", "2 haftada bir Pazartesi", "Her ayın son günü", "Her yıl 15 Mart". */ export function describeRecurrence( r: Pick, ): string { const { y, m, d } = parse(r.startDate); const every = (unit: string) => r.interval === 1 ? `Her ${unit}` : `${r.interval} ${unit}da bir`; switch (r.frequency) { case "daily": return r.interval === 1 ? "Her gün" : `${r.interval} günde bir`; case "weekly": { const wd = WEEKDAYS[new Date(Date.UTC(y, m - 1, d)).getUTCDay()]!; return r.interval === 1 ? `Her hafta ${wd}` : `${r.interval} haftada bir ${wd}`; } case "monthly": if (r.monthlyMode === "last") return r.interval === 1 ? "Her ayın son günü" : `${r.interval} ayda bir, ayın son günü`; return r.interval === 1 ? `Her ay ${d}. gün` : `${every("ay")}, ${d}. gün`; case "yearly": return r.interval === 1 ? `Her yıl ${d} ${MONTHS[m - 1]}` : `${r.interval} yılda bir, ${d} ${MONTHS[m - 1]}`; } }