/*************************GO-LICENSE-START*********************************
* Copyright 2014 ThoughtWorks, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*************************GO-LICENSE-END***********************************/
package com.thoughtworks.go.util;
import java.text.SimpleDateFormat;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.Days;
import org.joda.time.Hours;
import org.joda.time.Minutes;
import org.joda.time.Seconds;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import static com.thoughtworks.go.util.DateUtils.formatISO8601;
@Component
public class TimeConverter {
static final int HOUR_IN_SECONDS = 60 * 60;
static final int DAY_IN_SECONDS = 24 * 60 * 60;
static final int MONTH_IN_SECONDS = 30 * DAY_IN_SECONDS;
static final int YEAR_IN_SECONDS = 365 * DAY_IN_SECONDS;
public static final ConvertedTime OVER_X_YEARS_AGO = new ConvertedTime("label.over.x.years",
"over $time years ago");
public static final ConvertedTime ABOUT_1_YEAR_AGO = new ConvertedTime("label.about.1.year", "about 1 year ago");
public static final ConvertedTime ABOUT_X_MONTHS_AGO = new ConvertedTime("label.x.months",
"$time months ago");
public static final ConvertedTime ABOUT_1_MONTH_AGO =
new ConvertedTime("label.1.month", "about 1 month ago");
public static final ConvertedTime ABOUT_X_DAYS_AGO = new ConvertedTime("label.x.days",
"$time days ago");
public static final ConvertedTime ABOUT_1_DAY_AGO = new ConvertedTime("label.1.day", "1 day ago");
public static final ConvertedTime ABOUT_X_HOURS_AGO = new ConvertedTime("label.x.hours",
"about $time hours ago");
public static final ConvertedTime ABOUT_1_HOUR_AGO = new ConvertedTime("label.1.hour", "about 1 hour ago");
public static final ConvertedTime ABOUT_X_MINUTES_AGO = new ConvertedTime("label.x.minutes",
"$time minutes ago");
public static final ConvertedTime ABOUT_1_MINUTE_AGO = new ConvertedTime("label.1.minute", "1 minute ago");
public static final ConvertedTime LESS_THAN_A_MINUTE_AGO = new ConvertedTime("label.less.1.minute",
"less than a minute ago");
public final DateTimeFormatter dateFormatterWithTimeZone = DateTimeFormat.forPattern("dd MMM, yyyy 'at' HH:mm:ss [Z]");
public final DateTimeFormatter dateFormatter = DateTimeFormat.forPattern("dd MMM, yyyy 'at' HH:mm:ss");
private static final LinkedHashMap<Seconds, ConvertableTime> RULES =
new LinkedHashMap<>();
static {
RULES.put(Seconds.seconds(29), new TimeConverter.LessThanAMinute());
RULES.put(Minutes.minutes(1).toStandardSeconds().plus(Seconds.seconds(29)),
new TimeConverter.AboutOneMinute());
RULES.put(Minutes.minutes(44).toStandardSeconds().plus(Seconds.seconds(29)),
new TimeConverter.From2To44Minutes());
RULES.put(Minutes.minutes(89).toStandardSeconds().plus(Seconds.seconds(29)),
new TimeConverter.AboutOneHour());
RULES.put(Hours.hours(23).toStandardMinutes().plus(Minutes.minutes(59)).toStandardSeconds().plus(
Seconds.seconds(29)), new TimeConverter.About2To24Hours());
RULES.put(Hours.hours(47).toStandardMinutes().plus(Minutes.minutes(59)).toStandardSeconds().plus(
Seconds.seconds(29)), new TimeConverter.AboutOneDay());
RULES.put(Days.days(29).toStandardHours().plus(Hours.hours(23)).toStandardMinutes().plus(
Minutes.minutes(59)).toStandardSeconds().plus(Seconds.seconds(29)),
new TimeConverter.From2To29Days());
RULES.put(Days.days(59).toStandardHours().plus(Hours.hours(23)).toStandardMinutes().plus(
Minutes.minutes(59)).toStandardSeconds().plus(Seconds.seconds(29)),
new TimeConverter.AboutOneMonth());
RULES.put(Days.days(365).toStandardSeconds().minus(Seconds.seconds(31)),
new TimeConverter.From2To12Month());
RULES.put(Days.days(730).toStandardSeconds().minus(Seconds.seconds(31)),
new TimeConverter.AboutOneYear());
}
public ConvertedTime getConvertedTime(long duration) {
Set<Seconds> keys = RULES.keySet();
for (Seconds seconds : keys) {
if (duration <= seconds.getSeconds()) {
return RULES.get(seconds).getConvertedTime(duration);
}
}
return new TimeConverter.OverTwoYears().getConvertedTime(duration);
}
public ConvertedTime getConvertedTime(Date dateFrom) {
return dateFrom == null ? ConvertedTime.NOT_AVAILABLE : getConvertedTime(dateFrom, new Date());
}
public ConvertedTime getConvertedTime(String defaultMessage) {
return new ConvertedTime(defaultMessage);
}
public static String getHumanReadableDate(DateTime date) {
String dateString = getDateFormatterWithTimeZone().format(date.toDate());
int colonPlace = dateString.length() - 2;
return dateString.substring(0, colonPlace) + ":" + dateString.substring(colonPlace);
}
private static SimpleDateFormat getDateFormatterWithTimeZone() {
return new SimpleDateFormat("d MMM yyyy HH:mm 'GMT' Z", Locale.ENGLISH);
}
public String getHumanReadableString(Date date) {
return date == null ? ConvertedTime.NOT_AVAILABLE.toString() : dateFormatter.print(new DateTime(date));
}
public String getHumanReadableStringWithTimeZone(Date date) {
return date == null ? ConvertedTime.NOT_AVAILABLE.toString() : dateFormatterWithTimeZone.print(new DateTime(date));
}
public ConvertedTime getConvertedTime(Date dateLogFileGenerated, Date dateCheckTheDuration) {
if (dateCheckTheDuration.getTime() < dateLogFileGenerated.getTime()) {
String dateString = getHumanReadableDate(new DateTime(dateLogFileGenerated));
return new ConvertedTime(dateString);
} else {
return getConvertedTime((dateCheckTheDuration.getTime() - dateLogFileGenerated.getTime()) / 1000);
}
}
public String nullSafeDate(Date date) {
return date == null ? ConvertedTime.NOT_AVAILABLE.toString() : formatISO8601(date);
}
public static class ConvertedTime implements MessageSourceResolvable {
public static final ConvertedTime NO_HISTORICAL_DATA =
new TimeConverter.ConvertedTime("label.no.historical.data", "No historical data.");
public static final ConvertedTime NOT_AVAILABLE = new TimeConverter.ConvertedTime("N/A");
private String code;
private long arguments;
private String message;
public ConvertedTime(String code, long time, String message) {
this.message = message;
this.arguments = time;
this.code = code;
}
public ConvertedTime(String code, String message) {
this.message = message;
this.code = code;
}
public ConvertedTime(String message) {
this.message = message;
}
public String[] getCodes() {
return new String[]{code};
}
public Object[] getArguments() {
return new Long[]{arguments};
}
/**
* Create a new ConvertedTime instance based on this with new time value.
*/
public ConvertedTime argument(long time) {
String newMessage = StringUtils.replace(message, "$time", String
.valueOf(time));
return new ConvertedTime(code, time, newMessage);
}
public String getDefaultMessage() {
return message;
}
public int hashCode() {
return super.hashCode();
}
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ConvertedTime)) {
return false;
}
boolean eq = true;
ConvertedTime other = (ConvertedTime) obj;
eq &= ObjectUtils.nullSafeEquals(code, other.code);
eq &= ObjectUtils.nullSafeEquals(arguments, other.arguments);
eq &= ObjectUtils.nullSafeEquals(message, other.message);
return eq;
}
public String toString() {
return getDefaultMessage();
}
}
public static ConvertedTime convert(Date date) {
return new TimeConverter().getConvertedTime(date);
}
public static ConvertedTime convertHandleNull(Date date) {
if (date == null) { return TimeConverter.ConvertedTime.NO_HISTORICAL_DATA; }
return TimeConverter.convert(date);
}
static interface ConvertableTime {
ConvertedTime getConvertedTime(long duration);
}
static class LessThanAMinute implements ConvertableTime {
public ConvertedTime getConvertedTime(long duration) {
return LESS_THAN_A_MINUTE_AGO;
}
}
static class AboutOneMinute implements ConvertableTime {
public ConvertedTime getConvertedTime(long duration) {
return ABOUT_1_MINUTE_AGO;
}
}
static class From2To44Minutes implements ConvertableTime {
public ConvertedTime getConvertedTime(long duration) {
long time = (duration + 30) / 60;
return ABOUT_X_MINUTES_AGO.argument(time);
}
}
static class AboutOneHour implements ConvertableTime {
public ConvertedTime getConvertedTime(long duration) {
return ABOUT_1_HOUR_AGO;
}
}
static class About2To24Hours implements ConvertableTime {
public ConvertedTime getConvertedTime(long duration) {
long hours = (duration + 30 * 60 + 30) / TimeConverter.HOUR_IN_SECONDS;
long time = hours >= 23 ? 23 : hours;
return ABOUT_X_HOURS_AGO.argument(time);
}
}
static class AboutOneDay implements ConvertableTime {
public ConvertedTime getConvertedTime(long duration) {
return ABOUT_1_DAY_AGO;
}
}
static class From2To29Days implements ConvertableTime {
public ConvertedTime getConvertedTime(long duration) {
long time = (duration + 30) / TimeConverter.DAY_IN_SECONDS;
return ABOUT_X_DAYS_AGO.argument(time);
}
}
static class AboutOneMonth implements ConvertableTime {
public ConvertedTime getConvertedTime(long duration) {
return ABOUT_1_MONTH_AGO;
}
}
static class From2To12Month implements ConvertableTime {
public ConvertedTime getConvertedTime(long duration) {
long time = (duration + 30) / TimeConverter.MONTH_IN_SECONDS;
return ABOUT_X_MONTHS_AGO.argument(time);
}
}
static class AboutOneYear implements ConvertableTime {
public ConvertedTime getConvertedTime(long duration) {
return ABOUT_1_YEAR_AGO;
}
}
static class OverTwoYears implements ConvertableTime {
public ConvertedTime getConvertedTime(long duration) {
long time = (duration + 30)
/ TimeConverter.YEAR_IN_SECONDS;
return OVER_X_YEARS_AGO.argument(time);
}
}
}