// ── Date utilities ── // Uses moment.js which is bundled with Obsidian. // Period format helpers following O-vault conventions. import { moment } from 'obsidian'; export interface PeriodInfo { day: string; // yyyy-MM-dd week: string; // GGGG-[W]WW month: string; // yyyy-MM quarter: string; // yyyy-[Q]Q year: string; // yyyy } export interface CalendarDay { date: moment.Moment; dayOfMonth: number; isToday: boolean; isCurrentMonth: boolean; isoWeekNumber: number; } export interface CalendarWeek { weekNumber: number; days: CalendarDay[]; } /** * Get period info for a given date. */ export function getPeriodInfo(date: moment.Moment): PeriodInfo { return { day: date.format('YYYY-MM-DD'), week: date.format('GGGG-[W]WW'), month: date.format('YYYY-MM'), quarter: date.format('YYYY-[Q]Q'), year: date.format('YYYY'), }; } /** * Get quarter string from a moment date (e.g. "2026-Q3"). */ export function getQuarterString(date: moment.Moment): string { return date.format('YYYY-[Q]Q'); } /** * Get the current date's period info. */ export function getTodayPeriod(): PeriodInfo { return getPeriodInfo(moment()); } /** * Build a month grid as an array of weeks. * Each week has 7 days, starting on the configured first day of week. * ISO week numbers are calculated from the Monday of that week row. */ export function getMonthGrid( year: number, month: number, // 0-indexed (0 = January) firstDayOfWeek: number, // 0 = Sunday, 1 = Monday ): CalendarWeek[] { const firstOfMonth = moment({ year, month, day: 1 }); const lastOfMonth = moment(firstOfMonth).endOf('month'); // Find the first day to display (may go back into previous month) const startDate = moment(firstOfMonth) .subtract((firstOfMonth.day() - firstDayOfWeek + 7) % 7, 'days'); const today = moment().startOf('day'); const weeks: CalendarWeek[] = []; let currentDay = moment(startDate); while (currentDay.isBefore(lastOfMonth) || currentDay.month() === month) { const weekDays: CalendarDay[] = []; for (let i = 0; i < 7; i++) { weekDays.push({ date: moment(currentDay), dayOfMonth: currentDay.date(), isToday: currentDay.isSame(today, 'day'), isCurrentMonth: currentDay.month() === month, isoWeekNumber: currentDay.isoWeek(), }); currentDay.add(1, 'day'); } weeks.push({ weekNumber: weekDays[0].isoWeekNumber, days: weekDays, }); // Safety: break after 6 weeks if (weeks.length >= 6) break; } return weeks; } /** * Format a date label for the breadcrumb (e.g. "June 1st, 2026"). */ export function formatDateLabel(date: moment.Moment): string { return date.format('MMMM Do, YYYY'); } /** * Format a month label (e.g. "June 2026"). */ export function formatMonthLabel(date: moment.Moment): string { return date.format('MMMM YYYY'); } /** * Get readable quarter label (e.g. "Q3 2026"). */ export function formatQuarterLabel(date: moment.Moment): string { return date.format('[Q]Q YYYY'); } /** * Navigate a date by a given period and amount. */ export function navigateDate( date: moment.Moment, period: 'day' | 'week' | 'month' | 'quarter' | 'year', delta: number, ): moment.Moment { if (period === 'quarter') { return moment(date).add(delta * 3, 'months'); } return moment(date).add(delta, period); } /** * Create a note filename from a date using the given format string. */ export function formatPeriodName(date: moment.Moment, format: string): string { return date.format(format); } /** * Get ISO week number for a given date. */ export function getISOWeek(date: moment.Moment): number { return date.isoWeek(); }