date/utils.ts

/**
 * @packageDocumentation
 * @module date
 */

import { isNumber } from "../number/utils";
import { leadingTime } from "../string/utils";

/**
 * 날짜 포맷 옵션
 * @interface
 * @property format {string} at 정보를 변경할 문자 포맷 (예: YYYY-MM-DD AA hh -> 2020-01-01 오전 12시)
 * @property multiple {number} at 의 곱셈(at 정보를 unixtime 으로 다루는 경우 1000 을 옵션으로 지정)
 * @property alternative {string} at 정보가 invalid 할 때 대체 문자
 */
export interface DateFormatOption {
  format?: string;
  multiple?: number;
  alternative?: string;
  labelDays?: string[];
}

/**
 * 지정된 시간을 지정된 옵션의 포맷으로 변경
 * @param at 시간
 * @param options 옵션
 * @example
 * const at1 = 1553146437000; // new Date("2019-03-21 14:33:57").getTime();
 * const at2 = 1553146437; // unixtime
 * console.log(toFormat(at1)); // "2019-03-21 14:33:57"
 * console.log(toFormat(at1, { format: "YYYY-MM-DD" })); // "2019-03-21"
 * console.log(toFormat(at1, { format: "hh:mm:ss YYYY/MM/DD" })); // "14:33:57 2019/03/21"
 * console.log(toFormat(at2, { multiple: 1000 })); // "2019-03-21 14:33:57"
 * console.log(toFormat("", { alternative: "Unknown" })); // "Unknown"
 */
export function toFormat(at: number, options: DateFormatOption = {}): string {
  const {
    multiple = 1,
    alternative = "",
    format = "YYYY-MM-DD hh:mm:ss",
    labelDays = ["일", "월", "화", "수", "목", "금", "토"]
  }: DateFormatOption = options;
  let dateStr = alternative;
  if (isNumber(at)) {
    const date = new Date(at * multiple);
    const dtYear = date.getFullYear();
    const dtMonth = date.getMonth() + 1;
    const dtDate = date.getDate();
    const dtDay = date.getDay();
    const dtHour = date.getHours();
    const dtHourA = dtHour < 13 ? dtHour : dtHour - 12;
    const dtMinute = date.getMinutes();
    const dtSecond = date.getSeconds();
    let dtAA = dtHour < 13 ? "오전" : "오후";

    dateStr = format;
    dateStr = dateStr.replace(/YYYY/, dtYear.toString());
    dateStr = dateStr.replace(/MM/, leadingTime(dtMonth));
    dateStr = dateStr.replace(/DD/, leadingTime(dtDate));
    if (/AA/.test(dateStr) === true) {
      dateStr = dateStr.replace(/AA/, dtAA);
      dateStr = dateStr.replace(/hh/, leadingTime(dtHourA));
    } else {
      dateStr = dateStr.replace(/hh/, leadingTime(dtHour));
    }
    if (/d/.test(dateStr) === true) {
      dateStr = dateStr.replace(/d/, labelDays[dtDay]);
    }
    dateStr = dateStr.replace(/mm/, leadingTime(dtMinute));
    dateStr = dateStr.replace(/ss/, leadingTime(dtSecond));
  }
  return dateStr;
}

/**
 * 지난시간 포맷 옵션
 * @interface
 * @property justMax? {number} 방금 전으로 표시될 최대 시간(초)
 * @property justLabel? {string} '방금 전' 라벨
 * @property minuteMax? {number} 'n분 전' 으로 표시될 최대 시간(초)
 * @property minuteLabel? {string} '분 전' 라벨
 * @property hourMax? {number} 'n시간 전' 으로 표시될 최대 시간(초)
 * @property hourLabel? {string} '시간 전' 라벨
 * @property dayMax? {number} 'n일 전' 으로 표시될 최대 시간(초)
 * @property dayLabel? {string} '일 전' 라벨
 * @property format? {string} dayMax 도 넘어가는 시간인 경우 표시될 날짜 포맷
 */
export interface DatePastOption {
  justMax?: number;
  justLabel?: string;
  minuteMax?: number;
  minuteLabel?: string;
  hourMax?: number;
  hourLabel?: string;
  dayMax?: number;
  dayLabel?: string;
  format?: string;
  alternative?: any;
}

/**
 * pastAt 이 fromAt 으로 부터 지나간 시간을 지정된 옵션에 따라 반환.
 * @param fromAt 기준 시간
 * @param pastAt 비교할 시간
 * @param options 옵션
 * @example
 * const now = new Date("2019-03-12 08:10:20").getTime();
 * console.log(toPast(now, new Date("2019-03-12 08:09:21").getTime())); // '방금 전'
 * console.log(toPast(now, new Date("2019-03-12 08:00:20").getTime())); // '10분 전'
 * console.log(toPast(now, new Date("2019-03-12 07:10:20").getTime())); // '1시간 전'
 * console.log(toPast(now, new Date("2019-03-11 08:10:21").getTime())); // '23시간 전'
 * console.log(toPast(now, new Date("2019-03-10 08:10:20").getTime())); // '2일 전'
 * console.log(toPast(now, new Date("2019-02-10 08:10:19").getTime(), { format: "YYYY년 MM월 DD일"})); // "2019년 02월 10일"
 */
export function toPast(
  fromAt: number,
  pastAt: number,
  options: DatePastOption = {}
): string {
  const {
    justMax = 60,
    minuteMax = 3600,
    hourMax = 86400,
    dayMax = 2592000,
    justLabel = "방금 전",
    minuteLabel = "분 전",
    hourLabel = "시간 전",
    dayLabel = "일 전",
    format = "YYYY-MM-DD hh:mm:ss",
    alternative = "Unknown"
  }: DatePastOption = options;

  if (!pastAt || !fromAt || fromAt <= pastAt) {
    return alternative;
  }

  const pastSecond = Math.floor((fromAt - pastAt) / 1000);
  const pastMinute = Math.floor(pastSecond / 60);
  const pastHour = Math.floor(pastMinute / 60);
  const pastDay = Math.floor(pastHour / 24);

  if (pastSecond < justMax) return justLabel;
  if (pastSecond < minuteMax) return `${pastMinute}${minuteLabel}`;
  if (pastSecond < hourMax) return `${pastHour}${hourLabel}`;
  if (pastSecond <= dayMax) return `${pastDay}${dayLabel}`;
  return toFormat(pastAt, { format });
}