OKKY를 참고한 게시판을 만들고있다. OKKY의 게시글의 작성일을 보면 구체적 날짜가 아닌 '2분 전', '약 1시간 전', '약 1개월 전' 등으로 작성일로부터 얼마나 지났는지를 표시해주고 있다.
어떻게 하는 지 검색을 해 보니 Javascript 함수로 만든 블로그 글들이 가장 많이 보였다. 그러나 나는 JSP를 사용하고 있기 때문에 어떤 식으로 적용하는 것이 좋을 지 생각해보았다.
Javascript : 컨트롤러에서 Model
에 담아 JSTL <c:forEach>
반복문을 통해 게시글 목록을 출력하고 있기 때문에 적합하지 않다. JSP(JSTL) 동작이 완료된 뒤에 브라우저에서 Javascript가 동작하기 때문에 둘을 결합시키는 것이 적절하지 않아보였다.
JSP 스크립트릿 : <%! %>
안에 getElapsedTime()
메서드를 선언하고 <%= getElapsedTime(${post.createdDate}) %>
와 같이 메서드를 선언할 수 있다고 생각했으나... JSTL 태그를 통해 메서드에 파라미터를 전달할 수가 없었다.
커스텀 JSTL 태그 : JSTL 태그 값을 사용하려면 커스텀 태그 라이브러리를 추가해야겠다고 생각했다. 그런데 나는 JSP 사용을 권장하지 않는 스프링 부트를 사용하고 있기 때문에 정보가 부족했다. 다른 방식을 찾는게 좋을 것 같았다.
컨트롤러에서 변환 : 작성일을 '5분 전'과 같은 형식으로 표시하는 것은 View의 역할이기 때문에 컨트롤러에서 변환해서 전달하는 것은 좋아보이지 않았다. 또한 다른 View에서는 동일한 값을 다른 형식으로 표시하고 싶을 수도 있기 때문이다. 컨트롤러는 원본을 주는 것이 맞다고 생각해서 이 방법은 소거했다.
(해결!) Public 메서드 선언 : 사실 문제는 간단했다. 생각해보니 JSP에서 Member
와 같은 클래스도 import
도 가능하고 메서드도 쓸 수 있었다. 그래서 web
패키지에 JspFunction
클래스를 만들고 그 안에 getElapsedTime()
메서드를 선언했다.
Javascript 예시에서는 날짜가 millisecond timestamp 형식이었다. 그런데 나는 DB에 작성일(createdDate
)을 LocalDateTime
타입으로 저장하고 있었기 때문에 timestamp 형식으로 변환해야 했다.
Timestamp는 협정 세계시(UTC) 1970년 1월 1일 자정을 기준으로 얼마나 많은 초(혹은 밀리초)가 지났는 지를 표시한다. 음수를 사용하여 그 이전의 날짜도 표기할 수 있다. 단순한 숫자이기 때문에 기간을 계산하기에 편리하다. Epoch Time 이라고도 한다.
LocalDateTime
은 어느 지역을 기준으로 했는지 타임존(time zone)에 대한 정보가 없기 때문에 epoch time으로 직접 변경할 수가 없다. 따라서 ZonedDateTime
형식으로 변환하고 타임존 정보를 주어야 한다. atZone()
을 통해 타임존을 설정할 수 있고, toEpochSecond()
로 epoch time으로 변경할 수 있으며 반환 타입은 long
이다.
public class JspFunction {
public String getElapsedTime(LocalDateTime createdTime) {
LocalDateTime now = LocalDateTime.now();
long nowMS = now.atZone(ZoneId.of("Asia/Seoul")).toEpochSecond();
long createdMS = createdTime.atZone(ZoneId.of("Asia/Seoul")).toEpochSecond();
}
}
long seconds = nowMS - createdMS;
if (seconds < 60) {
return "방금 전";
}
long minutes = seconds / 60;
if (minutes < 60) {
return minutes + "분 전";
}
long hours = minutes / 60;
if (hours < 24) {
return hours + "시간 전";
}
Javascript 코드의 경우 timestamp 타입이 밀리초 단위이기 때문에 1000으로 나누어 주었지만 Java는 초 단위이기 때문에 생략해도 된다.
long days = hours / 24;
long weeks = ChronoUnit.WEEKS.between(createdTime, now);
long months = ChronoUnit.MONTHS.between(createdTime, now);
long years = ChronoUnit.YEARS.between(createdTime, now);
if (days < 7) {
return days + "일 전";
}
if (months < 1) {
return "약 " + weeks + "주 전";
}
if (months < 12) {
return "약 " + months + "개월 전";
}
return "약 " + years + "년 전";
시간을 계산하는 것 까지는 문제가 없지만, 주 단위부터는 문제가 생긴다. 달마다 일수가 다르고 매 해의 일수도 딱 떨어지는게 아니기 때문이다.
다행히도 ChronoUnit
클래스로 아주 쉽게 문제가 해결된다. between()
메서드를 사용하면 주, 개월, 년 단위로 기간을 쉽게 계산할 수 있다.
<jsp:useBean id="customFn" class="com.knou.board.web.JspFunction"/>
<jsp:useBean>
액션태그를 통해 JspFunction
클래스를 JSP 파일로 가져온다.
<ul class="list-group list-group-flush list-unstyled">
<c:forEach var="post" items="${posts}">
<li class="py-3 border-top text-decoration-none">
<div class="x-text-sm">
<a href="#">${post.author.nickname}</a>
<span class="mx-1">·</span>
<span>${customFn.getElapsedTime(post.createdDate)}</span>
</div>
<div class="my-2">
<a href="#"><span class="x-font-semibold">${post.title}</span></a>
</div>
<div class="d-flex justify-content-end x-text-md" style="line-height: 1.2rem">
<i class="me-1 bi bi-eye" style="font-size: 1.2rem"></i><span
class="x-text-sm">${post.viewCount}</span>
</div>
</li>
</c:forEach>
</ul>
${customFn.getElapsedTime(post.createdDate)}
와 같이 JSTL을 사용하여 간편하게 출력 성공하였다.
function getElapsedTime(createdTime) {
const now = new Date();
const created = new Date(createdTime);
const seconds = Math.floor((now - created) / 1000); // Milliseconds -> Seconds
if (seconds < 60) {
return "방금 전";
}
const minutes = Math.floor(seconds / 60);
if (minutes < 60) {
return minutes + "분 전";
}
const hours = Math.floor(minutes / 60);
if (hours < 24) {
return hours + "시간 전";
}
const days = Math.floor(hours / 24);
const weeks = Math.floor(days / 7);
const months = Math.floor(days / 30.43685); // 한 달은 약 30.43685일
const years = Math.floor(months / 12);
if (days < 7) {
return days + "일 전";
}
if (months < 1) {
return "약 " + weeks + "주 전";
}
if (months < 12) {
return "약 " + months + "개월 전";
}
return "약 " + years + "년 전";
}
Ajax 요청에 의한 응답 데이터를 가지고 View 처리를 하는 경우 <jsp:useBean>
을 사용할 수 없다. 따라서 똑같은 기능을 하는 Javascript 함수를 만들어주었다.
Java의 toEpochSecond()
는 EpochTime을 초 단위로 만들어주는 반면 Javascript의 Date
객체는 밀리초 단위의 EpochTime을 갖는다. 따라서 초 단위로 계산하기 위해서는 1000으로 나누어주는 단계가 필요했다.
또한 두 날짜 사이의 년수 및 개월수 계산의 경우 Java에서는 ChronoUnit
을 사용했는데 JQuery에서는 이러한 기능을 제공해주는 메서드가 없는 것 같았다. 어차피 정확한 계산이 필요하지 않기 때문에 days
를 월의 평균 일수로 나누어 months
를 대략적으로 계산하였다. 월의 평균 일수는 1년 365.2422일을 12개월로 나누어 구했다.
작성일을 경과 시간으로 표시해주는 것은 날짜 그대로 표시하는 것보다 얼마나 지난 글인지 직관적으로 알 수 있다는 점이 좋은 것 같다.
다만 OKKY는 글 상세보기를 해도 날짜가 표시되지 않는다. 게시글의 날짜를 정확하게 알아야 할 이유는 딱히 없지만 그래도 있는 것이 사용자 입장에서는 좋다. 너무 오래되어서 '21년 이상 전'과 같은 글을 보면 날짜가 너무 두루뭉술해서 답답함이 느껴지기도 한다.
아마도 DB와 사용자의 타임존 문제 때문이 아닐까 한다. 앞서 말한 것처럼 사용자가 게시글의 정확한 날짜를 알 필요도 없고 개발자 입장에서는 타임존을 통일하면 편하기 때문 아닐까...?