라이브러리 없이 날짜 표기 함수 구현하기 (TypeScript)

예약 서비스를 개발하면 예약 시작 시각과 종료 시각, 무료 취소 기한 등을 나타내기 위해 api로 호출한 날짜 값을 정해진 표기법으로 변환해야 할 일이 많다.

api에서 호출하는 날짜는 주로 timestamp 타입으로 정의되어 있는데, grpc를 사용할 경우 google.type.TimeOfDay 타입으로 사용하기도 한다. api에서 호출하는 값이 아닌 자바스크립트에서 현재 시각을 받을 때는 Date 타입을 사용하게 되기도 한다. Timestamp 타입과 TimeOfDay 타입은 다음과 같다.

message Timestamp {
	// Represents seconds of UTC time since Unix epoch
	// 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
	// 9999-12-31T23:59:59Z inclusive.
	int64 seconds = 1;
	// Non-negative fractions of a second at nanosecond resolution. Negative
	// second values with fractions must still have non-negative nanos values
	// that count forward in time. Must be from 0 to 999,999,999
	// inclusive.
	int32 nanos = 2;
}
message Date {
	// Year of the date. Must be from 1 to 9999, or 0 to specify a date without
	// a year.
	int32 year = 1;
	// Month of a year. Must be from 1 to 12, or 0 to specify a year without a
	// month and day.
	int32 month = 2;
	// Day of a month. Must be from 1 to 31 and valid for the year and month, or 0
	// to specify a year by itself or a year and month where the day isn't
	// significant.
	int32 day = 3;
}

원래는 이 함수가 date-fns라는 라이브러리를 사용하도록 되어있었는데 번들 사이즈가 너무 커서 직접 구현하는 게 좋겠다는 리뷰를 받아 직접 구현하는 것으로 변경했다.

파라미터는 Date 타입으로 받도록 했고, 날짜 형식은 통일하는 것이 개발이나 UX 관점에서 통일성을 유지하기에 좋겠지만 다르게 써야하는 경우가 있어 날자 형식도 주로 사용하는 형식을 기본값으로 주고 다를 경우 파라미터로 받을 수 있도록 했다.

const convertDateToStringWithDay = (
  date: Date,
  dateFormat = 'yy.M.d.(E)'
) => {

한글로 요일을 받을 수 있어야 하기 때문에 각 요일을 한글로 index가 0인 일요일부터 표기하고, 월의 index도 0부터 시작하기 때문에 date.getMonth에서 1을 더해주도록 했다.

const dayNames = ['일', '월', '화', '수', '목', '금', '토']

const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
const hour = date.getHours()
const minute = date.getMinutes()
const second = date.getSeconds()
const dayName = dayNames[date.getDay()]

받는 날짜 형식에 따라 연도가 4글자/2글자일 경우와 요일은 한 글자일 경우와 나머지는 두 글자/한 글자일 경우를 나눠서 월/일/시간/분/초는 두 글자로 표기해야할 경우 한 자리 수일 때 앞에 0을 붙여서 표현하도록 했다.

const dateObject: { [key: string]: string } = {
  yyyy: year.toString(),
  yy: year.toString().slice(2),
  MM: ('0' + month).slice(-2),
  M: month.toString(),
  dd: ('0' + day).slice(-2),
  d: day.toString(),
  hh: ('0' + hour).slice(-2),
  h: hour.toString(),
  mm: ('0' + minute).slice(-2),
  m: minute.toString(),
  ss: ('0' + second).slice(-2),
  s: second.toString(),
  E: dayName,
} 

그리고는 마지막으로 정규표현식을 사용해 정의된 표기법에서 각 문자열에 대응하는 실제 값을 매칭해서 보여주도록 했다. 정규표현식의 마지막에 /gi 플래그를 넣음으로써 전역에서 대소문자 구문 없이 모든 값을 대체하도록 했다.

return dateFormat.replace(
  /yyyy|yy|MM|M|dd|d|hh|h|mm|m|ss|s|E/gi,
  (match) => dateObject[match]
)

그래서 최종적으로 완성된 함수는 다음과 같다.

export const convertDateToStringWithDay = (
  date: Date,
  dateFormat = 'yy.M.d.(E)'
) => {
  const dayNames = ['일', '월', '화', '수', '목', '금', '토']

  const year = date.getFullYear()
  const month = date.getMonth() + 1
  const day = date.getDate()
  const hour = date.getHours()
  const minute = date.getMinutes()
  const second = date.getSeconds()
  const dayName = dayNames[date.getDay()]

  const dateObject: { [key: string]: string } = {
    yyyy: year.toString(),
    yy: year.toString().slice(2),
    MM: ('0' + month).slice(-2),
    M: month.toString(),
    dd: ('0' + day).slice(-2),
    d: day.toString(),
    hh: ('0' + hour).slice(-2),
    h: hour.toString(),
    mm: ('0' + minute).slice(-2),
    m: minute.toString(),
    ss: ('0' + second).slice(-2),
    s: second.toString(),
    E: dayName,
  }

  return dateFormat.replace(
    /yyyy|yy|MM|M|dd|d|hh|h|mm|m|ss|s|E/gi,
    (match) => dateObject[match]
  )
}

Written by@jaeeun
I explain with words and code. I explain with words and code. I explain with words and code.