/*
* Copyright (c) 2009-2010 Lockheed Martin Corporation
*
* 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.
*/
package org.eurekastreams.commons.formatting;
import java.util.Date;
/**
* Formatting utility that creates display dates. Since it needs to work on both the client and server which do not
* share common date formatting routines (GWT can't use SimpleDateFormat and the server can't use GWT routines), it
* implements it's own (for now).
*/
// TODO: Inject this with a date access strategy to allow it to use Java and GWT date routines. The strategy used on the
// server would also take account of the user's timezone (explicitly; handled automatically on client).
// TODO: Via the strategies, handle locales.
public class DateFormatter
{
/** MILLISECONDS_PER_SECOND. */
private static final int MILLISECONDS_PER_SECOND = 1000;
/** MILLISECONDS_PER_MINUTE. */
private static final int MILLISECONDS_PER_MINUTE = 60 * 1000;
/** MILLISECONDS_PER_HOUR. */
private static final int MILLISECONDS_PER_HOUR = 60 * 60 * 1000;
/** MILLISECONDS_PER_DAY. */
private static final int MILLISECONDS_PER_DAY = (24 * 60 * 60 * 1000);
/** Names of week days. */
private static final String[] DAY_OF_WEEK_NAMES = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
"Friday", "Saturday" };
/** Names of months. */
private static final String[] MONTH_NAMES = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct",
"Nov", "Dec" };
/** For converting Java years. */
private static final int YEAR_BASE = 1900;
/**
* Go off the same Date instance so multiple calls to timeAgo is consistent.
*/
private Date baseDate;
/**
* Constructor.
*/
public DateFormatter()
{
this(new Date());
}
/**
* Constructor.
*
* @param inBaseDate
* the date to base the 'ago' on - usually should be set as new Date().
*/
public DateFormatter(final Date inBaseDate)
{
baseDate = inBaseDate;
}
/**
* Creates a formatted representation of a given date/time, using the supplied current date/time as a reference.
*
* @param theDate
* The date to represent.
* @param nowDate
* "Now" reference.
* @param brief
* If a brief form at should be used.
* @return Formatted date.
*/
@SuppressWarnings("deprecation")
public static String timeAgo(final Date theDate, final Date nowDate, final boolean brief)
{
StringBuffer sb = new StringBuffer("");
long deltaMilliseconds = nowDate.getTime() - theDate.getTime();
// check for yesterday
// This trumps the "agos".
if (deltaMilliseconds < 2 * MILLISECONDS_PER_DAY)
{
// CANNOT just use a delta! Need to actually make sure the day of the event is within the day prior to
// today. Consider: if it is 8:30AM now, yesterday = everything from 8.5 to 32.5 hours ago. Using 24-48
// hours ago means that some of the day before yesterday will be labeled as yesterday which is just plain
// wrong.
int dayOfWeekDelta = nowDate.getDay() - theDate.getDay();
if (dayOfWeekDelta == 1 || dayOfWeekDelta == 1 - 7)
{
if (brief)
{
return "Yesterday";
}
else
{
sb.append("Yesterday at ");
appendTimeOfDay(sb, theDate);
return sb.toString();
}
}
}
if (deltaMilliseconds < MILLISECONDS_PER_MINUTE)
{
sb.append("Less than one minute ago");
}
else if (deltaMilliseconds < 2 * MILLISECONDS_PER_MINUTE)
{
sb.append("1 minute ago");
}
else if (deltaMilliseconds < MILLISECONDS_PER_HOUR)
{
sb.append(deltaMilliseconds / MILLISECONDS_PER_MINUTE);
sb.append(" minutes ago");
}
else if (deltaMilliseconds < 2 * MILLISECONDS_PER_HOUR)
{
sb.append("1 hour ago");
}
else if (deltaMilliseconds < MILLISECONDS_PER_DAY)
{
sb.append(deltaMilliseconds / MILLISECONDS_PER_HOUR);
sb.append(" hours ago");
}
else
{
// for older than yesterday
// For the past week, use the day of week name + time. But cut it off such that if today is Tuesday, that
// last Tuesday doesn't fall under this rule. That way there's no confusion about which Tuesday it's talking
// about.
if (deltaMilliseconds < 7 * MILLISECONDS_PER_DAY && nowDate.getDay() != theDate.getDay())
{
appendDayOfWeek(sb, theDate);
}
else
{
// For older dates, use the month/day + time, and add the year on the end if it's not from this year.
appendDate(sb, theDate, theDate.getYear() != nowDate.getYear());
}
if (!brief)
{
sb.append(" at ");
appendTimeOfDay(sb, theDate);
}
}
return sb.toString();
}
/**
* Appends the day of week to the string being built.
*
* @param sb
* String builder.
* @param theDate
* Date being formatted.
*/
@SuppressWarnings("deprecation")
private static void appendDayOfWeek(final StringBuffer sb, final Date theDate)
{
sb.append(DAY_OF_WEEK_NAMES[theDate.getDay()]);
}
/**
* Appends the date to the string being built.
*
* @param sb
* String builder.
* @param theDate
* Date being formatted.
* @param withYear
* If the year should be shown.
*/
@SuppressWarnings("deprecation")
private static void appendDate(final StringBuffer sb, final Date theDate, final boolean withYear)
{
sb.append(MONTH_NAMES[theDate.getMonth()]);
sb.append(' ');
sb.append(theDate.getDate());
if (withYear)
{
sb.append(", ");
sb.append(theDate.getYear() + YEAR_BASE);
}
}
/**
* Appends the time of day to the string being built.
*
* @param sb
* String builder.
* @param theDate
* Date being formatted.
*/
@SuppressWarnings("deprecation")
private static void appendTimeOfDay(final StringBuffer sb, final Date theDate)
{
final int halfDay = 12;
int hour = theDate.getHours();
boolean pm = false;
if (hour == 0)
{
hour = halfDay;
}
else if (hour >= halfDay)
{
pm = true;
if (hour > halfDay)
{
hour -= halfDay;
}
}
sb.append(hour);
sb.append(':');
int minute = theDate.getMinutes();
if (minute <= 9)
{
sb.append('0');
}
sb.append(minute);
sb.append(pm ? "pm" : "am");
}
/**
* Creates a formatted representation of a given date/time, using the supplied current date/time as a reference.
*
* @param theDate
* The date to represent.
* @return Formatted date.
*/
public String timeAgo(final Date theDate)
{
return timeAgo(theDate, baseDate, false);
}
/**
* Creates a formatted representation of a given date/time, using the supplied current date/time as a reference.
*
* @param theDate
* The date to represent.
* @param brief
* If a brief form at should be used.
* @return Formatted date.
*/
public String timeAgo(final Date theDate, final boolean brief)
{
return timeAgo(theDate, baseDate, brief);
}
}