245 lines
6.1 KiB
TypeScript
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
|
|
}
|
|
}
|