최근 SNS의 영향으로 많은 사이트에서 댓글이나 간단한 게시글의 작성 시간을 표현할때 몇시간전, 몇분전, 몇초전 등의 훨씬 인식하기 쉬운 시간표현을 하고 있다. 저번에 썼던, ‘최근에 내가 만드는 CMS’에서도 시간 표현에 그런 표현을 아주 많이 썼다. 많이 쓰다보니 쉽게 적용할 방법이 필요했고 그러다보니 쉽게 함수로 만들었다.

이 함수는 한 6개월전에 만들었다. 첨에 이런 함수를 쓰는 법에 대한 가이드 같은게 구글링을 해봐도 어디도 없었다. 지금도 별로 보이지 않는 것 같다. 그래서 이것저것 생각을 해봐서 제일 편하게 쓸 수 있는 방법이 span 태그에 timestamp 라는 attribute 를 추가해서 하는게 편할 것 같아서 이렇게 만들었다. 이것저것 수정을 하다보니 소스는 조금 조잡해져서 수정이 필요하다. 타 라이브러리에서 범용성을 위해 지원하는 함수들을 의존하면서 구현한거라 성능이 조금 구리긴하다. 그래도 우선 지금 쓰는 스크립트 정도는 공개 한다.

원리

<span class="_timestamp" timestamp="1312265402">2011년 8월 2일 15시 10분 02초</span>

위와 같이 html을 입력하면 자동으로 몇초전 등의 표현으로 바뀐다. 자바스크립트로 바꾸는 것이며 검색엔진 봇이 인식 할 수는 없는 방식이다. 그렇기 때문에 span 태그의 innerHTML에 저렇게 실제 날짜 표현을 해줘야 검색엔진 크롤러 봇들이 제대로 시간을 퍼갈 수 있다.
몇초전 등의 표현으로 바뀌는 원리는, 우선 setInterval 등의 함수로 페이지내에서 시간을 계산해주는 함수가 정기적으로 실행된다. 그 함수는 현재 페이지에서 ‘span._timestamp’라는 요소가 존재하는지 찾고, 존재하면 그 요소의 ‘timestamp’라는 Attribute(속성)이 있는지 한번 더 찾는다. 속성이 있으면 timestamp 를 기반으로 현재 시간과 비교하여 몇초 다른지 몇시간 다른지 계산을 해준다. 계산을 하면 그 span._timestamp 에 innerHTML을 몇초전 등의 시간으로 표현해준다.

의존성 함수

jQuery

jQuery 는 1.6으로 테스트 됐다. 다른 버전도 잘 될지는 테스트를 안해봐서 모르겠으며, 만약 잘 되지 않더라도 이 함수는 간단한 놈이기 때문에 약간의 함수 수정만 거치면 어떤 버전에서도 가능하다.

php.js

phpjs는 php의 함수들을 javascript 에서 사용 하도록 구현해놓은 라이브러리이다. 여기서는 2개의 함수가 필요하다. date() 함수와 strtotime() 함수를 쓴다. 두 함수는 아래의 링크에서 받을 수 있다.

자바스크립트 Date Object 에 메소드 추가

Date.prototype.getDOY = function() {
	var onejan = new Date(this.getFullYear(),0,1);
	return Math.ceil((this - onejan) / 86400000);
 
}

Day Of Year 이라는 함수이며 이 함수가 필요하다.

실시간 시간표현 함수

우선 시간 계산을 하는 자바스크립트이다. 아래는 date() 함수에만 의존성이 있다.

function dynamicDate(dt, option, nocalcserverdif){
	if (typeof timeDif == "undefined") timeDif = 0;
 
	if (!nocalcserverdif)
	{
		dt += timeDif;
	}
	var dif = Math.floor(new Date().getTime() / 1000) - dt;
 
	switch(option)
	{
		case "half":
			if (dif >= 0)
			{
				var today = new Date();
 
				var seconds = dif%60,
					minutes = ((dif-seconds)%3600)/60,
					hours = ((dif-(minutes*60+seconds))%86400)/3600;
 
				var days = today.getDOY() - date("z", dt),
					months = (today.getFullYear()*12 + today.getMonth() + 1) - (parseInt(date("Y",dt)*12) + parseInt(date("n", dt))),
					years = today.getFullYear() - date("Y", dt);
 
				if (years == 0 || (years == 1 && months <= 1))
				{
					if (months == 0 || (months == 1 && days < 7))
					{
						if (days == 0)
						{
							if (hours == 0)
							{
								if (minutes == 0)
								{
									if (seconds <= 2)
									{
										return "방금 전";
									}
									else
									{
										return seconds+"초 전";
									}
								}
								else
								{
									return minutes+"분 "+seconds+"초 전";
								}
							}
							else
							{
								return hours+"시간 "+minutes+"분 전";
							}
						}
						else if (days == 1)
						{
							return "어제 "+date("H시", dt);
						}
						else if (days == 2)
						{
							return "그저께 "+date("H시", dt);
						}
						else if (days == 3)
						{
							return "그끄저께 "+date("H시", dt);
						}
						else if (days < 7)
						{
							var weekStr = ['일','월','화','수','목','금','토'];
							return weekStr[date('w', dt)]+"요일 "+date("H시", dt);
						}
						else
						{
							return days+"일 전 "+date("H시", dt);
						}
					}
					else if (months == 1)
					{
						return "저번달 "+date("j일", dt);
					}
					else if (months < 6)
					{
						return months+"달 전 "+date("j일", dt);
					}
					else
					{
						return date("n월 j일", dt);
					}
				}
				else if (years == 1)
				{
					return "작년 "+date("n", dt)+"월 "+date("j일", dt);
				}
				else if (years == 2)
				{
					return "재작년 "+date("n", dt)+"월 "+date("j일", dt);
				}
				else
				{
					return years+"년 전 "+date("n", dt)+"월 "+date("j일", dt);
				}
			}
			else
			{
				dif = dif * -1;
				var today = new Date();
 
				var seconds = dif%60,
					minutes = ((dif-seconds)%3600)/60,
					hours = ((dif-(minutes*60+seconds))%86400)/3600;
 
				var days = date("z", dt) - today.getDOY(),
					months = (parseInt(date("Y",dt)*12) + parseInt(date("n", dt))) - (today.getFullYear()*12 + today.getMonth() + 1),
					years = date("Y", dt) - today.getFullYear();
 
				if (years == 0 || (years == 1 && months <= 1))
				{
					if (months == 0 || (months == 1 && days < 7))
					{
						if (days == 0)
						{
							if (hours == 0)
							{
								if (minutes == 0)
								{
									if (seconds <= 2)
									{
										return "곧";
									}
									else
									{
										return seconds+"초 후";
									}
								}
								else
								{
									return minutes+"분 "+seconds+"초 후";
								}
							}
							else
							{
								return hours+"시간 "+minutes+"분 후";
							}
						}
						else if (days == 1)
						{
							return "내일 "+date("H시", dt);
						}
						else if (days == 2)
						{
							return "모레 "+date("H시", dt);
						}
						else
						{
							return days+"일 후 "+date("H시", dt);
						}
					}
					else if (months == 1)
					{
						return "다음달 "+date("j일", dt);
					}
					else if (months < 6)
					{
						return months+"달 후 "+date("j일", dt);
					}
					else
					{
						return date("n월 j일", dt);
					}
				}
				else if (years == 1)
				{
					return "내년 "+date("n", dt)+"월 "+date("j일", dt);
				}
				else if (years == 2)
				{
					return "내후년 "+date("n", dt)+"월 "+date("j일", dt);
				}
				else
				{
					return years+"년 후 "+date("n", dt)+"월 "+date("j일", dt);
				}
			}
			break;
 
		default:
			if (dif >= 0)
			{
				var today = new Date();
 
				var seconds = dif%60,
					minutes = ((dif-seconds)%3600)/60,
					hours = ((dif-(minutes*60+seconds))%86400)/3600;
 
				var days = today.getDOY() - date("z", dt),
					months = (today.getFullYear()*12 + today.getMonth() + 1) - (parseInt(date("Y",dt)*12) + parseInt(date("n", dt))),
					years = today.getFullYear() - date("Y", dt);
 
				if (years == 0 || (years == 1 && months <= 1))
				{
					if (months == 0 || (months == 1 && days < 7))
					{
						if (days == 0)
						{
							if (hours == 0)
							{
								if (minutes == 0)
								{
									if (seconds <= 2)
									{
										return "방금 전";
									}
									else
									{
										return seconds+"초 전";
									}
								}
								else
								{
									return minutes+"분 "+seconds+"초 전";
								}
							}
							else
							{
								return hours+"시간 "+minutes+"분 전";
							}
						}
						else if (days == 1)
						{
							return "어제 "+date("H시 i분", dt);
						}
						else if (days == 2)
						{
							return "그저께 "+date("H시 i분", dt);
						}
						else if (days == 3)
						{
							return "그끄저께 "+date("H시 i분", dt);
						}
						else if (days < 7)
						{
							var weekStr = ['일','월','화','수','목','금','토'];
							return weekStr[date('w', dt)]+"요일 "+date("H시 i분", dt);
						}
						else
						{
							return days+"일 전 "+date("H시 i분", dt);
						}
					}
					else if (months == 1)
					{
						return "저번달 "+date("j일 H시", dt);
					}
					else if (months < 6)
					{
						return months+"달 전 "+date("j일 H시", dt);
					}
					else
					{
						return date("n월 j일 H시", dt);
					}
				}
				else if (years == 1)
				{
					return "작년 "+date("n", dt)+"월 "+date("j일 H시", dt);
				}
				else if (years == 2)
				{
					return "재작년 "+date("n", dt)+"월 "+date("j일 H시", dt);
				}
				else
				{
					return years+"년 전 "+date("n", dt)+"월 "+date("j일 H시", dt);
				}
			}
			else
			{
				dif = dif * -1;
				var today = new Date();
 
				var seconds = dif%60,
					minutes = ((dif-seconds)%3600)/60,
					hours = ((dif-(minutes*60+seconds))%86400)/3600;
 
				var days = date("z", dt) - today.getDOY(),
					months = (parseInt(date("Y",dt)*12) + parseInt(date("n", dt))) - (today.getFullYear()*12 + today.getMonth() + 1),
					years = date("Y", dt) - today.getFullYear();
 
				if (years == 0 || (years == 1 && months <= 1))
				{
					if (months == 0 || (months == 1 && days < 7))
					{
						if (days == 0)
						{
							if (hours == 0)
							{
								if (minutes == 0)
								{
									if (seconds <= 2)
									{
										return "곧";
									}
									else
									{
										return seconds+"초 후";
									}
								}
								else
								{
									return minutes+"분 "+seconds+"초 후";
								}
							}
							else
							{
								return hours+"시간 "+minutes+"분 후";
							}
						}
						else if (days == 1)
						{
							return "내일 "+date("H시 i분", dt);
						}
						else if (days == 2)
						{
							return "모레 "+date("H시 i분", dt);
						}
						else if (days < 7)
						{
							var weekStr = ['일','월','화','수','목','금','토'];
							return weekStr[date('w', dt)]+"요일 "+date("H시 i분", dt);
						}
						else
						{
							return days+"일 후 "+date("H시 i분", dt);
						}
					}
					else if (months == 1)
					{
						return "다음달 "+date("j일 H시", dt);
					}
					else if (months < 6)
					{
						return months+"달 후 "+date("j일 H시", dt);
					}
					else
					{
						return date("n월 j일 H시", dt);
					}
				}
				else if (years == 1)
				{
					return "내년 "+date("n", dt)+"월 "+date("j일 H시", dt);
				}
				else if (years == 2)
				{
					return "내후년 "+date("n", dt)+"월 "+date("j일 H시", dt);
				}
				else
				{
					return years+"년 후 "+date("n", dt)+"월 "+date("j일 H시", dt);
				}
			}
	}
}

잘 되는지 테스트해보려면 아래와 같이 테스트하면 된다. 1312265402는 Unix Timestamp 값이다.

alert(dynamicDate(1312265402));

그리고 실시간으로 업데이트 해주는 함수는 아래와 같다. strtotime 함수와 jQuery 에 의존성이 있다.

jQuery(function(){
	refreshDate = function(){
		$("span._timestamp").each(function(i,o){
			var oT = $(o);
			var timestamp = oT.attr("timestamp");
			var date = oT.attr("date");
			if (date) timestamp = strtotime(date);
			var nocalcserverdif = false;
			timestamp = parseInt(timestamp);
			if (isNaN(timestamp)) return true;
			if (oT.attr("clientside")) nocalcserverdif = true;
			oT.html(dynamicDate(timestamp, oT.attr('option'), nocalcserverdif));
		});
	}
 
	refreshDate();
	setInterval(function(){refreshDate()}, 5000);
});

이런식으로 하면 된다. 바로 위의 이것은 jQuery(function(){})에 꼭 넣어서 써야 한다. 페이지와 jQuery가 로딩완료 된 후 실행되어야 하는 함수이다. 5초마다 시간을 갱신해준다.

사용법

이제 어디서든 아래의 php 소스를 입력하면 시간이 자동으로 표현된다. option이라는 attribute 는 지정을 안해도 되고 half 등을 쓸 수 있다.

$time = time();
echo '<span class="_timestamp" timestamp="'.$time.'" option="half">'.date("Y/m/d H:i:s", $time).'</span>

혹은 아래와 같이 써도 표현이 된다. 여기서 strtotime() 함수를 쓰게 된다.

<span class="_timestamp" date="2011-08-02 15:34:02">2011-08-02 15:34:02</span>

표현 예제

2011-08-02 15:34:02는 지금으로부터 2011-08-02 15:34:02 입니다.

추가 조치

그리고 span 태그를 쓰게 되면 간혹 어떤 브라우저에서는 css 를 바로 윗 요소의 style 을 잘 반영 해주지 않는 경우가 있다. 또는 이니셜라이즈 CSS를 쓸 경우, 모든 요소에 기본 글자 색깔과 font-size 를 지정해주는 경우도 있어서 아래의 css 를 추가해주면 좀더 유연한 작동을 바랄 수 있다.

span._timestamp	{ color: inherit; font-family: inherit; font-size: inherit; }

파일 다운로드 및 데모 보기

데모페이지: http://awhile.us/demo/dynamicdate/
다운로드: [다운로드]