/** * ISO 8601 주차 계산 composable * - 1월 4일이 포함된 주 = 1주차 * - 주의 시작 = 월요일 * - 예: 2026년 2주차 = 2026-01-05(월) ~ 2026-01-11(일) */ export interface WeekInfo { year: number week: number startDate: Date endDate: Date startDateStr: string endDateStr: string weekString: string // "2026-W01" 형식 } export function useWeekCalc() { /** * 날짜를 YYYY-MM-DD 형식으로 포맷 */ function formatDate(date: Date): string { const y = date.getFullYear() const m = String(date.getMonth() + 1).padStart(2, '0') const d = String(date.getDate()).padStart(2, '0') return `${y}-${m}-${d}` } /** * 한국어 날짜 포맷 (M월 D일) */ function formatDateKr(date: Date): string { return `${date.getMonth() + 1}월 ${date.getDate()}일` } /** * 해당 날짜의 월요일 반환 */ function getMonday(date: Date): Date { const d = new Date(date) d.setHours(0, 0, 0, 0) const day = d.getDay() const diff = d.getDate() - day + (day === 0 ? -6 : 1) d.setDate(diff) return d } /** * 해당 날짜의 일요일 반환 */ function getSunday(date: Date): Date { const monday = getMonday(date) const sunday = new Date(monday) sunday.setDate(monday.getDate() + 6) return sunday } /** * 해당 연도의 1주차 월요일 반환 (ISO 8601) * - 1월 4일이 포함된 주의 월요일 */ function getWeek1Monday(year: number): Date { const jan4 = new Date(year, 0, 4) return getMonday(jan4) } /** * 해당 연도의 총 주차 수 반환 */ function getWeeksInYear(year: number): number { const dec31 = new Date(year, 11, 31) const weekInfo = getWeekNumber(dec31) // 12월 31일이 다음 해 1주차면 52주, 아니면 해당 주차 return weekInfo.year === year ? weekInfo.week : 52 } /** * 특정 날짜의 ISO 주차 번호 반환 */ function getWeekNumber(date: Date): { year: number; week: number } { const d = new Date(date) d.setHours(0, 0, 0, 0) // 목요일 기준으로 연도 판단 (ISO 8601) const thursday = new Date(d) thursday.setDate(d.getDate() + 3 - ((d.getDay() + 6) % 7)) const year = thursday.getFullYear() const week1Monday = getWeek1Monday(year) const diffTime = getMonday(d).getTime() - week1Monday.getTime() const diffDays = Math.round(diffTime / (24 * 60 * 60 * 1000)) const week = Math.floor(diffDays / 7) + 1 return { year, week } } /** * 특정 날짜의 ISO 주차 정보 반환 */ function getWeekInfo(date: Date = new Date()): WeekInfo { const monday = getMonday(date) const sunday = getSunday(date) const { year, week } = getWeekNumber(date) return { year, week, startDate: monday, endDate: sunday, startDateStr: formatDate(monday), endDateStr: formatDate(sunday), weekString: `${year}-W${week.toString().padStart(2, '0')}` } } /** * 연도/주차로 날짜 범위 반환 */ function getWeekDates(year: number, week: number): WeekInfo { const week1Monday = getWeek1Monday(year) const monday = new Date(week1Monday) monday.setDate(week1Monday.getDate() + (week - 1) * 7) const sunday = new Date(monday) sunday.setDate(monday.getDate() + 6) return { year, week, startDate: monday, endDate: sunday, startDateStr: formatDate(monday), endDateStr: formatDate(sunday), weekString: `${year}-W${week.toString().padStart(2, '0')}` } } /** * 주차 이동 (delta: +1 다음주, -1 이전주) */ function changeWeek(year: number, week: number, delta: number): { year: number; week: number } { let newYear = year let newWeek = week + delta if (newWeek < 1) { newYear-- newWeek = getWeeksInYear(newYear) } else if (newWeek > getWeeksInYear(newYear)) { newYear++ newWeek = 1 } return { year: newYear, week: newWeek } } /** * 이번 주 정보 (보고서 기준) * - 금~일: 현재 주차 * - 월~목: 이전 주차 */ function getCurrentWeekInfo(): WeekInfo { const today = new Date() const dayOfWeek = today.getDay() // 0=일, 1=월, ..., 5=금, 6=토 // 월~목 (1~4)이면 이전 주차 기준 if (dayOfWeek >= 1 && dayOfWeek <= 4) { const lastWeek = new Date(today) lastWeek.setDate(today.getDate() - 7) return getWeekInfo(lastWeek) } // 금~일 (5, 6, 0)이면 현재 주차 기준 return getWeekInfo(today) } /** * 실제 이번 주 정보 (달력 기준, 항상 현재 주차) */ function getActualCurrentWeekInfo(): WeekInfo { return getWeekInfo(new Date()) } /** * 지난 주 정보 */ function getLastWeekInfo(): WeekInfo { const lastWeek = new Date() lastWeek.setDate(lastWeek.getDate() - 7) return getWeekInfo(lastWeek) } /** * 주차 문자열 파싱 */ function parseWeekString(weekStr: string): { year: number; week: number } | null { const match = weekStr.match(/^(\d{4})-W(\d{2})$/) if (!match) return null return { year: parseInt(match[1]), week: parseInt(match[2]) } } /** * 주차별 날짜 범위 텍스트 (예: "1월 5일 ~ 1월 11일") */ function getWeekRangeText(year: number, week: number): string { const { startDate, endDate } = getWeekDates(year, week) return `${formatDateKr(startDate)} ~ ${formatDateKr(endDate)}` } /** * 주차별 날짜 범위 텍스트 - ISO 형식 (예: "2026-01-05 ~ 2026-01-11") */ function getWeekRangeTextISO(year: number, week: number): string { const { startDateStr, endDateStr } = getWeekDates(year, week) return `${startDateStr} ~ ${endDateStr}` } return { // 기본 유틸 formatDate, formatDateKr, getMonday, getSunday, // 주차 계산 getWeekNumber, getWeekInfo, getWeekDates, getWeeksInYear, changeWeek, // 현재/지난주 getCurrentWeekInfo, getActualCurrentWeekInfo, getLastWeekInfo, // 파싱/포맷 parseWeekString, getWeekRangeText, getWeekRangeTextISO } }