1. 개요
상대적인 시간과 두 시점 사이의 기간을 계산하는 것은 소프트웨어 시스템에서 일반적인 사용 사례입니다. 예를 들어 소셜 미디어 플랫폼에 새 사진을 게시하는 것과 같은 이벤트 이후 얼마나 많은 시간이 흘렀는지 사용자에게 보여주고 싶을 수 있습니다. 이러한 "시간 전" 텍스트의 예로는 "5분 전", "1년 전" 등이 있습니다.
단어의 의미와 선택은 완전히 상황에 따라 다르지만 전체적인 아이디어는 동일합니다.
이 사용방법(예제)에서는 Java에서 시간 전을 계산하기 위한 몇 가지 솔루션을 살펴봅니다. Java 8에 새로운 날짜 및 시간 API가 도입 되었기 때문에 버전 7과 버전 8에 대한 솔루션을 별도로 논의할 것입니다.
2. 자바 버전 7
Java 7에는 시간과 관련된 여러 클래스가 있습니다. 또한 Java 7 Date API의 단점으로 인해 여러 타사 시간 및 날짜 라이브러리도 사용할 수 있습니다.
먼저 순수한 Java 7을 사용하여 "시간 전"을 계산해 보겠습니다.
2.1. 순수한 자바 7
서로 다른 시간 세분성을 보유하고 이를 밀리초로 변환 하는 Enum 을 정의합니다 .
public enum TimeGranularity {
SECONDS {
public long toMillis() {
return TimeUnit.SECONDS.toMillis(1);
}
}, MINUTES {
public long toMillis() {
return TimeUnit.MINUTES.toMillis(1);
}
}, HOURS {
public long toMillis() {
return TimeUnit.HOURS.toMillis(1);
}
}, DAYS {
public long toMillis() {
return TimeUnit.DAYS.toMillis(1);
}
}, WEEKS {
public long toMillis() {
return TimeUnit.DAYS.toMillis(7);
}
}, MONTHS {
public long toMillis() {
return TimeUnit.DAYS.toMillis(30);
}
}, YEARS {
public long toMillis() {
return TimeUnit.DAYS.toMillis(365);
}
}, DECADES {
public long toMillis() {
return TimeUnit.DAYS.toMillis(365 * 10);
}
};
public abstract long toMillis();
}
시간 변환을 위한 강력한 도구인 java.util.concurrent.TimeUnit enum 을 사용 했습니다. TimeUnit Enum을 사용 하여 TimeGranularity Enum 의 각 값에 대해 toMillis() 추상 메서드를 재정의하여 각 값에 해당하는 밀리초 수를 반환하도록 합니다. 예를 들어 "decade"의 경우 3650일에 대한 밀리초 수를 반환합니다.
TimeGranularity Enum 을 정의한 결과 두 가지 메서드를 정의할 수 있습니다. 첫 번째는 java.util.Date 객체와 TimeGranularity 의 인스턴스를 가져 오고 "time ago" 문자열을 반환합니다.
static String calculateTimeAgoByTimeGranularity(Date pastTime, TimeGranularity granularity) {
long timeDifferenceInMillis = getCurrentTime() - pastTime.getTime();
return timeDifferenceInMillis / granularity.toMillis() + " " +
granularity.name().toLowerCase() + " ago";
}
이 메서드는 현재 시간과 주어진 시간의 차이를 밀리초 단위의 TimeGranularity 값으로 나눕니다. 결과적으로 지정된 시간 단위로 주어진 시간 이후 경과된 시간을 대략적으로 계산할 수 있습니다.
현재 시간을 얻기 위해 getCurrentTime() 메서드를 사용 했습니다. 테스트를 위해 고정된 시점을 반환하고 로컬 시스템에서 시간을 읽는 것을 피합니다. 실제로 이 메서드는 System.currentTimeMillis() 또는 LocalDateTime.now()를 사용하여 현재 시간의 실제 값을 반환합니다.
방법을 테스트해 보겠습니다.
Assert.assertEquals("5 hours ago",
TimeAgoCalculator.calculateTimeAgoByTimeGranularity(
new Date(getCurrentTime() - (5 * 60 * 60 * 1000)), TimeGranularity.HOURS));
또한 가장 적합한 시간 세분성을 자동으로 감지하고 보다 인간 친화적인 출력을 반환하는 메서드를 작성할 수도 있습니다.
static String calculateHumanFriendlyTimeAgo(Date pastTime) {
long timeDifferenceInMillis = getCurrentTime() - pastTime.getTime();
if (timeDifferenceInMillis / TimeGranularity.DECADES.toMillis() > 0) {
return "several decades ago";
} else if (timeDifferenceInMillis / TimeGranularity.YEARS.toMillis() > 0) {
return "several years ago";
} else if (timeDifferenceInMillis / TimeGranularity.MONTHS.toMillis() > 0) {
return "several months ago";
} else if (timeDifferenceInMillis / TimeGranularity.WEEKS.toMillis() > 0) {
return "several weeks ago";
} else if (timeDifferenceInMillis / TimeGranularity.DAYS.toMillis() > 0) {
return "several days ago";
} else if (timeDifferenceInMillis / TimeGranularity.HOURS.toMillis() > 0) {
return "several hours ago";
} else if (timeDifferenceInMillis / TimeGranularity.MINUTES.toMillis() > 0) {
return "several minutes ago";
} else {
return "moments ago";
}
}
이제 테스트를 통해 사용 예를 살펴보겠습니다.
Assert.assertEquals("several hours ago",
TimeAgoCalculator.calculateHumanFriendlyTimeAgo(new Date(getCurrentTime() - (5 * 60 * 60 * 1000))));
문맥에 따라 "적은", "여러", "많은" 또는 정확한 값과 같은 다른 단어를 사용할 수 있습니다.
2.2. 조다 타임 라이브러리
Java 8이 출시되기 전에 Joda-Time 은 Java의 다양한 시간 및 날짜 관련 작업에 대한 사실상의 표준이었습니다. Joda-Time 라이브러리의 세 가지 클래스를 사용하여 "시간 전"을 계산할 수 있습니다.
- org.joda.time.Period 는 org.joda.time.DateTime 의 두 개체를 가져와 이 두 시점 간의 차이를 계산합니다.
- org.joda.time.format.PeriodFormatter 는 기간 객체 를 인쇄하기 위한 형식을 정의합니다.
- 사용자 정의 PeriodFormatter 를 생성하기 위한 빌더 클래스인 org.joda.time.format.PeriodFormatuilder
이 세 가지 클래스를 사용하여 현재와 과거 시간 사이의 정확한 시간을 쉽게 얻을 수 있습니다.
static String calculateExactTimeAgoWithJodaTime(Date pastTime) {
Period period = new Period(new DateTime(pastTime.getTime()), new DateTime(getCurrentTime()));
PeriodFormatter formatter = new PeriodFormatterBuilder().appendYears()
.appendSuffix(" year ", " years ")
.appendSeparator("and ")
.appendMonths()
.appendSuffix(" month ", " months ")
.appendSeparator("and ")
.appendWeeks()
.appendSuffix(" week ", " weeks ")
.appendSeparator("and ")
.appendDays()
.appendSuffix(" day ", " days ")
.appendSeparator("and ")
.appendHours()
.appendSuffix(" hour ", " hours ")
.appendSeparator("and ")
.appendMinutes()
.appendSuffix(" minute ", " minutes ")
.appendSeparator("and ")
.appendSeconds()
.appendSuffix(" second", " seconds")
.toFormatter();
return formatter.print(period);
}
샘플 사용법을 살펴보겠습니다.
Assert.assertEquals("5 hours and 1 minute and 1 second",
TimeAgoCalculator.calculateExactTimeAgoWithJodaTime(new Date(getCurrentTime() - (5 * 60 * 60 * 1000 + 1 * 60 * 1000 + 1 * 1000))));
보다 인간 친화적인 출력을 생성하는 것도 가능합니다.
static String calculateHumanFriendlyTimeAgoWithJodaTime(Date pastTime) {
Period period = new Period(new DateTime(pastTime.getTime()), new DateTime(getCurrentTime()));
if (period.getYears() != 0) {
return "several years ago";
} else if (period.getMonths() != 0) {
return "several months ago";
} else if (period.getWeeks() != 0) {
return "several weeks ago";
} else if (period.getDays() != 0) {
return "several days ago";
} else if (period.getHours() != 0) {
return "several hours ago";
} else if (period.getMinutes() != 0) {
return "several minutes ago";
} else {
return "moments ago";
}
}
테스트를 실행하여 이 메서드가 보다 인간에게 친숙한 "time ago" 문자열을 반환하는지 확인할 수 있습니다.
Assert.assertEquals("several hours ago",
TimeAgoCalculator.calculateHumanFriendlyTimeAgoWithJodaTime(new Date(getCurrentTime() - (5 * 60 * 60 * 1000))));
다시 말하지만 사용 사례에 따라 "하나", "몇 개" 또는 "여러"와 같은 다른 용어를 사용할 수 있습니다.
2.3. Joda-Time TimeZone
Joda-Time 라이브러리를 사용하여 "시간 전" 계산에 시간대를 추가하는 것은 매우 간단합니다.
String calculateZonedTimeAgoWithJodaTime(Date pastTime, TimeZone zone) {
DateTimeZone dateTimeZone = DateTimeZone.forID(zone.getID());
Period period = new Period(new DateTime(pastTime.getTime(), dateTimeZone), new DateTime(getCurrentTimeByTimeZone(zone)));
return PeriodFormat.getDefault().print(period);
}
getCurrentTimeByTimeZone() 메서드 는 지정된 시간대의 현재 시간 값을 반환합니다. 테스트를 위해 이 메서드는 고정된 시점을 반환하지만 실제로는 Calendar.getInstance(zone).getTimeInMillis() 또는 LocalDateTime.now(zone)를 사용하여 현재 시간의 실제 값을 반환해야 합니다.
3. 자바 8
Java 8 은 Joda-Time 라이브러리의 많은 아이디어를 채택한 새롭게 개선된 날짜 및 시간 API 를 도입했습니다. 기본 java.time.Duration 및 java.time.Period 클래스를 사용하여 "시간 전"을 계산할 수 있습니다.
static String calculateTimeAgoWithPeriodAndDuration(LocalDateTime pastTime, ZoneId zone) {
Period period = Period.between(pastTime.toLocalDate(), getCurrentTimeByTimeZone(zone).toLocalDate());
Duration duration = Duration.between(pastTime, getCurrentTimeByTimeZone(zone));
if (period.getYears() != 0) {
return "several years ago";
} else if (period.getMonths() != 0) {
return "several months ago";
} else if (period.getDays() != 0) {
return "several days ago";
} else if (duration.toHours() != 0) {
return "several hours ago";
} else if (duration.toMinutes() != 0) {
return "several minutes ago";
} else if (duration.getSeconds() != 0) {
return "several seconds ago";
} else {
return "moments ago";
}
}
위의 코드 스니펫은 시간대를 지원하고 기본 Java 8 API만 사용합니다.
4. PrettyTime 라이브러리
PrettyTime은 특히 i18n 지원과 함께 "시간 전" 기능을 제공하는 강력한 라이브러리입니다 . 또한 사용자 정의가 가능하고 사용하기 쉬우며 Java 버전 7 및 8 모두에서 사용할 수 있습니다.
먼저 pom.xml 에 의존성 을 추가해 보겠습니다 .
<dependency>
<groupId>org.ocpsoft.prettytime</groupId>
<artifactId>prettytime</artifactId>
<version>3.2.7.Final</version>
</dependency>
이제 인간 친화적인 형식으로 "시간 전"을 얻는 것은 매우 쉽습니다.
String calculateTimeAgoWithPrettyTime(Date pastTime) {
PrettyTime prettyTime = new PrettyTime();
return prettyTime.format(pastTime);
}
5. Time4J 라이브러리
마지막으로 Time4J는 Java에서 시간 및 날짜 데이터를 조작하기 위한 또 다른 훌륭한 라이브러리입니다. 시간 전을 계산하는 데 사용할 수 있는 PrettyTime 클래스가 있습니다.
의존성 을 추가해 보겠습니다 .
<dependency>
<groupId>net.time4j</groupId>
<artifactId>time4j-base</artifactId>
<version>5.9</version>
</dependency>
<dependency>
<groupId>net.time4j</groupId>
<artifactId>time4j-sqlxml</artifactId>
<version>5.8</version>
</dependency>
이 의존성을 추가한 후 이전 시간을 계산하는 것은 매우 간단합니다.
String calculateTimeAgoWithTime4J(Date pastTime, ZoneId zone, Locale locale) {
return PrettyTime.of(locale).printRelative(pastTime.toInstant(), zone);
}
PrettyTime 라이브러리와 동일하게 Time4J는 기본적으로 i18n도 지원합니다.
6. 결론
이 기사에서는 Java에서 이전 시간을 계산하는 다양한 방법에 대해 설명했습니다.
순수 Java 및 타사 라이브러리 모두에 대한 솔루션이 있습니다. 새로운 날짜 및 시간 API가 Java 8에 도입되었으므로 순수한 Java 솔루션은 8 이전 버전과 이후 버전에서 다릅니다.
항상 그렇듯이 예제의 소스 코드는 GitHub에서 사용할 수 있습니다 .