Files
weeklyreport/frontend/composables/useWeekCalc.ts

245 lines
6.1 KiB
TypeScript

/**
* 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
}
}