modules_academic_yy-smr-wrapper.ts

/**
 * 학기 코드와 명칭을 정의하는 인터페이스
 */
export interface SmrConfig {
    cd: string;
    nm: string;
    order: number;
}

export const DEFAULT_SMR_CONFIGS: SmrConfig[] = [
    { cd: "10540010", nm: "1학기", order: 1 },
    { cd: "10540011", nm: "여름학기", order: 2 },
    { cd: "10540020", nm: "2학기", order: 3 },
    { cd: "10540021", nm: "겨울학기", order: 4 },
];

/**
 * 연도와 학기를 연산하고 관리하기 위한 래퍼 클래스
 *
 * @access private
 */
export class YySmr {
    private _yy: number;
    private _smr: string;
    private _configs: SmrConfig[];

    /**
     * @param yy       연도 (숫자 또는 숫자형 문자열)
     * @param smr      학기 코드
     * @param configs  커스텀 학기 설정 (선택 사항)
     */
    constructor(
        yy: number | string,
        smr: string,
        configs: SmrConfig[] = DEFAULT_SMR_CONFIGS,
    ) {
        this._yy = typeof yy === "string" ? parseInt(yy, 10) : yy;
        this._smr = smr;
        this._configs = [...configs].sort((a, b) => a.order - b.order);

        if (isNaN(this._yy)) {
            throw new Error(`Invalid year: ${yy}`);
        }
    }

    get yy(): number {
        return this._yy;
    }

    get smr(): string {
        return this._smr;
    }

    get smrNm(): string {
        const config = this._configs.find((c) => c.cd === this._smr);
        return config ? config.nm : "";
    }

    /**
     * 다음 학기를 반환합니다.
     *
     * @returns {YySmr}
     */
    next(): YySmr {
        const currentIndex = this._configs.findIndex((c) => c.cd === this._smr);
        if (currentIndex === -1) {
            throw new Error(
                `Cannot calculate next semester: Current semester code ${this._smr} is not in configs.`,
            );
        }

        if (currentIndex === this._configs.length - 1) {
            // 마지막 학기인 경우 다음 연도의 첫 학기로
            return new YySmr(this._yy + 1, this._configs[0].cd, this._configs);
        } else {
            return new YySmr(
                this._yy,
                this._configs[currentIndex + 1].cd,
                this._configs,
            );
        }
    }

    /**
     * 이전 학기를 반환합니다.
     *
     * @returns {YySmr}
     */
    prev(): YySmr {
        const currentIndex = this._configs.findIndex((c) => c.cd === this._smr);
        if (currentIndex === -1) {
            throw new Error(
                `Cannot calculate previous semester: Current semester code ${this._smr} is not in configs.`,
            );
        }

        if (currentIndex === 0) {
            // 첫 학기인 경우 이전 연도의 마지막 학기로
            return new YySmr(
                this._yy - 1,
                this._configs[this._configs.length - 1].cd,
                this._configs,
            );
        } else {
            return new YySmr(
                this._yy,
                this._configs[currentIndex - 1].cd,
                this._configs,
            );
        }
    }

    /**
     * 특정 학기만큼 이동한 결과를 반환합니다.
     *
     * @param offset  양수면 미래, 음수면 과거
     */
    add(offset: number): YySmr {
        let result: YySmr = new YySmr(this._yy, this._smr, this._configs);
        if (offset > 0) {
            for (let i = 0; i < offset; i++) {
                result = result.next();
            }
        } else if (offset < 0) {
            for (let i = 0; i > offset; i--) {
                result = result.prev();
            }
        }
        return result;
    }

    /**
     * 연도와 학기를 문자열로 반환합니다.
     *
     * @param separator  구분자 (기본값: '-')
     */
    toString(separator: string = "-"): string {
        return `${this._yy}${separator}${this._smr}`;
    }

    /**
     * 다른 YySmr 객체와 비교합니다.
     *
     * @param other
     * @returns {number} 0 이면 같음, 1 이면 본인이 큼, -1 이면 작음
     */
    compare(other: YySmr): number {
        if (this._yy !== other._yy) {
            return this._yy > other._yy ? 1 : -1;
        }

        const thisIdx = this._configs.findIndex((c) => c.cd === this._smr);
        const otherIdx = this._configs.findIndex((c) => c.cd === other._smr);

        if (thisIdx === otherIdx) return 0;
        return thisIdx > otherIdx ? 1 : -1;
    }
}