package cgeo.geocaching.utils;
import cgeo.geocaching.CgeoApplication;
import cgeo.geocaching.R;
import cgeo.geocaching.enumerations.CacheSize;
import cgeo.geocaching.enumerations.WaypointType;
import cgeo.geocaching.models.Geocache;
import cgeo.geocaching.models.PocketQuery;
import cgeo.geocaching.models.Waypoint;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.format.DateUtils;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import org.apache.commons.lang3.StringUtils;
public final class Formatter {
/** Text separator used for formatting texts */
public static final String SEPARATOR = " ยท ";
private Formatter() {
// Utility class
}
private static Context getContext() {
return CgeoApplication.getInstance().getBaseContext();
}
/**
* Generate a time string according to system-wide settings (locale, 12/24 hour)
* such as "13:24".
*
* @param date
* milliseconds since the epoch
* @return the formatted string
*/
@NonNull
public static String formatTime(final long date) {
return DateUtils.formatDateTime(getContext(), date, DateUtils.FORMAT_SHOW_TIME);
}
/**
* Generate a date string according to system-wide settings (locale, date format)
* such as "20 December" or "20 December 2010". The year will only be included when necessary.
*
* @param date
* milliseconds since the epoch
* @return the formatted string
*/
@NonNull
public static String formatDate(final long date) {
return DateUtils.formatDateTime(getContext(), date, DateUtils.FORMAT_SHOW_DATE);
}
/**
* Generate a date string according to system-wide settings (locale, date format)
* such as "20 December 2010". The year will always be included, making it suitable
* to generate long-lived log entries.
*
* @param date
* milliseconds since the epoch
* @return the formatted string
*/
@NonNull
public static String formatFullDate(final long date) {
return DateUtils.formatDateTime(getContext(), date, DateUtils.FORMAT_SHOW_DATE
| DateUtils.FORMAT_SHOW_YEAR);
}
/**
* Tries to get the date format pattern of the system short date.
*
* @return format pattern or empty String if it can't be retrieved
*/
@NonNull
public static String getShortDateFormat() {
final DateFormat dateFormat = android.text.format.DateFormat.getDateFormat(getContext());
if (dateFormat instanceof SimpleDateFormat) {
return ((SimpleDateFormat) dateFormat).toPattern();
}
return StringUtils.EMPTY; // should not happen
}
/**
* Generate a numeric date string according to system-wide settings (locale, date format)
* such as "10/20/2010".
*
* @param date
* milliseconds since the epoch
* @return the formatted string
*/
@NonNull
public static String formatShortDate(final long date) {
final DateFormat dateFormat = android.text.format.DateFormat.getDateFormat(getContext());
return dateFormat.format(date);
}
private static String formatShortDateIncludingWeekday(final long time) {
return DateUtils.formatDateTime(CgeoApplication.getInstance().getBaseContext(), time, DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_WEEKDAY) + ", " + formatShortDate(time);
}
/**
* Generate a numeric date string according to system-wide settings (locale, date format)
* such as "10/20/2010". Today and yesterday will be presented as strings "today" and "yesterday".
*
* @param date
* milliseconds since the epoch
* @return the formatted string
*/
@NonNull
public static String formatShortDateVerbally(final long date) {
final String verbally = formatDateVerbally(date);
if (verbally != null) {
return verbally;
}
return formatShortDate(date);
}
private static String formatDateVerbally(final long date) {
final int diff = CalendarUtils.daysSince(date);
switch (diff) {
case 0:
return CgeoApplication.getInstance().getString(R.string.log_today);
case 1:
return CgeoApplication.getInstance().getString(R.string.log_yesterday);
default:
return null;
}
}
/**
* Generate a numeric date and time string according to system-wide settings (locale,
* date format) such as "7 sept. at 12:35".
*
* @param date
* milliseconds since the epoch
* @return the formatted string
*/
@NonNull
public static String formatShortDateTime(final long date) {
return DateUtils.formatDateTime(getContext(), date, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL);
}
/**
* Generate a numeric date and time string according to system-wide settings (locale,
* date format) such as "7 september at 12:35".
*
* @param date
* milliseconds since the epoch
* @return the formatted string
*/
@NonNull
public static String formatDateTime(final long date) {
return DateUtils.formatDateTime(getContext(), date, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME);
}
@NonNull
public static String formatCacheInfoLong(final Geocache cache) {
final List<String> infos = new ArrayList<>();
if (StringUtils.isNotBlank(cache.getGeocode())) {
infos.add(cache.getGeocode());
}
addShortInfos(cache, infos);
if (cache.isPremiumMembersOnly()) {
infos.add(CgeoApplication.getInstance().getString(R.string.cache_premium));
}
if (cache.isOffline()) {
infos.add(CgeoApplication.getInstance().getString(R.string.cache_offline));
}
return StringUtils.join(infos, SEPARATOR);
}
@NonNull
public static String formatCacheInfoShort(final Geocache cache) {
final List<String> infos = new ArrayList<>();
addShortInfos(cache, infos);
return StringUtils.join(infos, SEPARATOR);
}
private static void addShortInfos(final Geocache cache, final List<String> infos) {
if (cache.hasDifficulty()) {
infos.add("D " + formatDT(cache.getDifficulty()));
}
if (cache.hasTerrain()) {
infos.add("T " + formatDT(cache.getTerrain()));
}
// don't show "not chosen" for events and virtuals, that should be the normal case
if (cache.getSize() != CacheSize.UNKNOWN && cache.showSize()) {
infos.add(cache.getSize().getL10n());
} else if (cache.isEventCache()) {
final Date hiddenDate = cache.getHiddenDate();
if (hiddenDate != null) {
infos.add(formatShortDateIncludingWeekday(hiddenDate.getTime()));
}
}
}
private static String formatDT(final float value) {
return String.format(Locale.getDefault(), "%.1f", value);
}
@NonNull
public static String formatCacheInfoHistory(final Geocache cache) {
final List<String> infos = new ArrayList<>(3);
infos.add(StringUtils.upperCase(cache.getGeocode()));
infos.add(formatDate(cache.getVisitedDate()));
infos.add(formatTime(cache.getVisitedDate()));
return StringUtils.join(infos, SEPARATOR);
}
@NonNull
public static String formatWaypointInfo(final Waypoint waypoint) {
final List<String> infos = new ArrayList<>(3);
final WaypointType waypointType = waypoint.getWaypointType();
if (waypointType != WaypointType.OWN && waypointType != null) {
infos.add(waypointType.getL10n());
}
if (waypoint.isUserDefined()) {
infos.add(CgeoApplication.getInstance().getString(R.string.waypoint_custom));
} else {
if (StringUtils.isNotBlank(waypoint.getPrefix())) {
infos.add(waypoint.getPrefix());
}
if (StringUtils.isNotBlank(waypoint.getLookup())) {
infos.add(waypoint.getLookup());
}
}
return StringUtils.join(infos, SEPARATOR);
}
@NonNull
public static String formatDaysAgo(final long date) {
final int days = CalendarUtils.daysSince(date);
switch (days) {
case 0:
return CgeoApplication.getInstance().getString(R.string.log_today);
case 1:
return CgeoApplication.getInstance().getString(R.string.log_yesterday);
default:
return CgeoApplication.getInstance().getResources().getQuantityString(R.plurals.days_ago, days, days);
}
}
/**
* Formatting of the hidden date of a cache
*
* @return {@code null} or hidden date of the cache (or event date of the cache) in human readable format
*/
@Nullable
public static String formatHiddenDate(final Geocache cache) {
final Date hiddenDate = cache.getHiddenDate();
if (hiddenDate == null) {
return null;
}
final long time = hiddenDate.getTime();
if (time <= 0) {
return null;
}
String dateString = formatFullDate(time);
if (cache.isEventCache()) {
// use today and yesterday strings
final String verbally = formatDateVerbally(time);
if (verbally != null) {
return verbally;
}
// otherwise use weekday and normal date
dateString = DateUtils.formatDateTime(CgeoApplication.getInstance().getBaseContext(), time, DateUtils.FORMAT_SHOW_WEEKDAY) + ", " + dateString;
}
// use just normal date
return dateString;
}
@NonNull
public static String formatMapSubtitle(final Geocache cache) {
return "D " + formatDT(cache.getDifficulty()) + SEPARATOR + "T " + formatDT(cache.getTerrain()) + SEPARATOR + cache.getGeocode();
}
@NonNull
public static String formatPocketQueryInfo(final PocketQuery pocketQuery) {
if (!pocketQuery.isDownloadable()) {
return StringUtils.EMPTY;
}
final List<String> infos = new ArrayList<>(3);
final int caches = pocketQuery.getCaches();
if (caches >= 0) {
infos.add(CgeoApplication.getInstance().getResources().getQuantityString(R.plurals.cache_counts, caches, caches));
}
final long lastGenerationTime = pocketQuery.getLastGenerationTime();
if (lastGenerationTime > 0) {
infos.add(Formatter.formatShortDateVerbally(lastGenerationTime));
}
final int daysRemaining = pocketQuery.getDaysRemaining();
if (daysRemaining == 0) {
infos.add(CgeoApplication.getInstance().getString(R.string.last_day_available));
} else {
infos.add(daysRemaining > 0 ? CgeoApplication.getInstance().getResources().getQuantityString(R.plurals.days_remaining, daysRemaining, daysRemaining) : StringUtils.EMPTY);
}
return StringUtils.join(infos, SEPARATOR);
}
public static String formatBytes(final long bytes) {
if (bytes < 1024) {
return bytes + " B";
}
final int exp = (int) (Math.log(bytes) / Math.log(1024));
final String pre = Character.toString("KMGTPE".charAt(exp - 1));
return String.format(Locale.getDefault(), "%.1f %sB", bytes / Math.pow(1024, exp), pre);
}
public static List<CharSequence> truncateCommonSubdir(@NonNull final List<CharSequence> directories) {
if (directories.size() < 2) {
return directories;
}
String commonEnding = directories.get(0).toString();
for (int i = 1; i < directories.size(); i++) {
final String directory = directories.get(i).toString();
while (!directory.endsWith(commonEnding)) {
final int offset = commonEnding.indexOf('/', 1);
if (offset == -1) {
return directories;
}
commonEnding = commonEnding.substring(offset);
}
}
final List<CharSequence> truncatedDirs = new ArrayList<>(directories.size());
for (final CharSequence title : directories) {
truncatedDirs.add(title.subSequence(0, title.length() - commonEnding.length() + 1) + "\u2026");
}
return truncatedDirs;
}
}