/*
* Copyright 2012 University of South Florida
*
* 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.onebusaway.android.directions.util;
import android.content.Context;
import android.preference.PreferenceManager;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.text.style.StrikethroughSpan;
import android.util.Log;
import org.onebusaway.android.R;
import org.onebusaway.android.util.ArrivalInfoUtils;
import org.onebusaway.android.util.PreferenceUtils;
import org.opentripplanner.api.model.Itinerary;
import org.opentripplanner.api.model.Leg;
import org.opentripplanner.routing.core.TraverseMode;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
/**
* @author Khoa Tran
* @author Simon Jacobs - conversion for OBA. Added Imperial measurements
*/
public class ConversionUtils {
private static final String TAG = "ConversionUtils";
private static final double FEET_PER_METER = 3.281;
/**
* Given a date string from an OTP server, parse it into a java Date.
*
* @param text string from OTP
* @return parsed date object, or null if there is an error with parsing.
*/
public static Date parseOtpDate(String text) {
try {
long ms = Long.parseLong(text);
return new Date(ms);
} catch (NumberFormatException ex) {
Log.e(TAG, "Error processing OTP response time text: " + text);
return null;
}
}
/**
* Given start time and end time strings, compute the delta between them.
* Strings should be in the OTP server return format, specified in OTPConstants
*
* @param startTimeText start time
* @param endTimeText end time
* @param applicationContext context to look up resources
* @return duration
*/
public static double getDuration(String startTimeText, String endTimeText,
Context applicationContext) {
double duration = 0;
Date startTime = parseOtpDate(startTimeText);
Date endTime = parseOtpDate(endTimeText);
if (startTime != null && endTime != null) {
if (PreferenceManager.getDefaultSharedPreferences(applicationContext)
.getInt(OTPConstants.PREFERENCE_KEY_API_VERSION, OTPConstants.API_VERSION_V1)
== OTPConstants.API_VERSION_V1){
duration = (endTime.getTime() - startTime.getTime());
} else {
duration = (endTime.getTime() - startTime.getTime()) / 1000;
}
}
return duration;
}
/**
* Return a formatted String for a distance. Should be in proper units according to
* preferences (either metric or imperial).
*
* @param meters distance in meters
* @param applicationContext context to look up resources
* @return formatted string of distance
*/
public static String getFormattedDistance(Double meters, Context applicationContext) {
String text = "";
if (PreferenceUtils.getUnitsAreMetricFromPreferences(applicationContext)) {
if (meters < 1000) {
text += String.format(OTPConstants.FORMAT_DISTANCE_METERS, meters) + " " + applicationContext
.getResources().getString(R.string.meters_abbreviation);
} else {
meters = meters / 1000;
text += String.format(OTPConstants.FORMAT_DISTANCE_KILOMETERS, meters) + " "
+ applicationContext.getResources().getString(R.string.kilometers_abbreviation);
}
} else {
double feet = meters * 3.281;
if (feet < 1000) {
text += String.format(OTPConstants.FORMAT_DISTANCE_METERS, feet) + " " + applicationContext
.getResources().getString(R.string.feet_abbreviation);
} else {
feet = feet / 5280;
text += String.format(OTPConstants.FORMAT_DISTANCE_KILOMETERS, feet) + " "
+ applicationContext.getResources().getString(R.string.miles_abbreviation);
}
}
return text;
}
/**
* Get a formatted string for a duration.
*
* @param sec duration in seconds
* @param applicationContext context to look up resources
* @return formatted duration string
*/
public static String getFormattedDurationText(long sec, Context applicationContext) {
String text = "";
long h = sec / 3600;
if (h >= 24) {
return null;
}
long m = (sec % 3600) / 60;
long s = (sec % 3600) % 60;
if (h > 0) {
text += Long.toString(h) + applicationContext.getResources()
.getString(R.string.hours_abbreviation);
}
text += Long.toString(m) + applicationContext.getResources()
.getString(R.string.minutes_abbreviation);
text += Long.toString(s) + applicationContext.getResources()
.getString(R.string.seconds_abbrevation);
return text;
}
/**
* Get duration text but disallow "seconds" units.
*
* @param sec duration in seconds
* @param longFormat true for long units ("minutes"), false for short units ("min")
* @param applicationContext context to look up resources
* @return formatted duration text
*/
public static String getFormattedDurationTextNoSeconds(long sec, boolean longFormat, Context applicationContext) {
String text = "";
long h = sec / 3600;
long m = (sec % 3600) / 60;
String longMinutes = applicationContext.getResources()
.getString(R.string.minutes_full);
String longMinutesSingular = applicationContext.getResources()
.getString(R.string.minutes_abbreviation);
String shortMinutes = applicationContext.getResources()
.getString(R.string.minutes_abbreviation);
if (longFormat) {
longMinutes = applicationContext.getResources()
.getString(R.string.minutes_full);
longMinutesSingular = applicationContext.getResources()
.getString(R.string.minute_singular);
}
String shortHours = applicationContext.getResources()
.getString(R.string.hours_abbreviation);
if (h > 0) {
text += Long.toString(h) + shortHours;
text += " " + Long.toString(m) + shortMinutes;
} else {
if (m == 0) {
text += "< 1 " + longMinutes;
} else if (m == 1 || m == -1) {
text += Long.toString(m) + " " + longMinutesSingular;
} else {
text += Long.toString(m) + " " + longMinutes;
}
}
return text;
}
public static List<Itinerary> fixTimezoneOffsets(List<Itinerary> itineraries,
boolean useDeviceTimezone) {
int agencyTimeZoneOffset = 0;
boolean containsTransitLegs = false;
if ((itineraries != null) && !itineraries.isEmpty()) {
ArrayList<Itinerary> itinerariesFixed = new ArrayList<Itinerary>(itineraries);
for (Itinerary it : itinerariesFixed) {
for (Leg leg : it.legs) {
if ((TraverseMode.valueOf((String) leg.mode)).isTransit()
&& !containsTransitLegs) {
containsTransitLegs = true;
}
if (leg.agencyTimeZoneOffset != 0) {
agencyTimeZoneOffset = leg.agencyTimeZoneOffset;
//If agencyTimeZoneOffset is different from 0, route contains transit legs
containsTransitLegs = true;
break;
}
}
}
if (useDeviceTimezone || !containsTransitLegs) {
agencyTimeZoneOffset = TimeZone.getDefault()
.getOffset(Long.parseLong(itinerariesFixed.get(0).startTime));
}
if (agencyTimeZoneOffset != 0) {
for (Itinerary it : itinerariesFixed) {
for (Leg leg : it.legs) {
leg.agencyTimeZoneOffset = agencyTimeZoneOffset;
}
}
}
return itinerariesFixed;
} else {
return itineraries;
}
}
public static CharSequence getTimeWithContext(Context applicationContext, int offsetGMT, long time,
boolean inLine) {
return getTimeWithContext(applicationContext, offsetGMT, time, inLine, -1);
}
public static CharSequence getTimeWithContext(Context applicationContext, int offsetGMT, long time,
boolean inLine, int color) {
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
DateFormat timeFormat = android.text.format.DateFormat.getTimeFormat(applicationContext);
DateFormat dateFormat = android.text.format.DateFormat.getDateFormat(applicationContext);
timeFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
cal.setTimeInMillis(time);
String noDeviceTimezoneNote = "";
if (offsetGMT != TimeZone.getDefault().getOffset(time)) {
noDeviceTimezoneNote = "GMT";
if (offsetGMT != 0) {
noDeviceTimezoneNote += offsetGMT / 3600000;
}
}
cal.add(Calendar.MILLISECOND, offsetGMT);
if (cal.get(Calendar.SECOND) >= 30) {
cal.add(Calendar.MINUTE, 1);
}
SpannableString spannableTime = new SpannableString(timeFormat.format(cal.getTime()));
if (color != -1) {
spannableTime.setSpan(new ForegroundColorSpan(color), 0,
spannableTime.length(), 0);
}
if (inLine) {
if (ConversionUtils.isToday(cal)) {
return TextUtils.concat(" ",
applicationContext
.getResources()
.getString(R.string.time_connector_before_time), " ", spannableTime,
" ", noDeviceTimezoneNote);
} else if (ConversionUtils.isTomorrow(cal)) {
return TextUtils.concat(" ",
applicationContext
.getResources()
.getString(R.string.time_connector_next_day), " ",
applicationContext.getResources()
.getString(R.string.time_connector_before_time), " ", spannableTime,
" ", noDeviceTimezoneNote);
} else {
return TextUtils.concat(" ", applicationContext
.getResources()
.getString(R.string.time_connector_before_date), " ",
dateFormat.format(cal.getTime()), " ",
applicationContext.getResources()
.getString(R.string.time_connector_before_time), " ", spannableTime,
" ", noDeviceTimezoneNote);
}
} else {
if (ConversionUtils.isToday(cal)) {
return TextUtils.concat(spannableTime, " ", noDeviceTimezoneNote);
} else if (ConversionUtils.isTomorrow(cal)) {
return TextUtils.concat(" ", spannableTime, ", ",
applicationContext
.getResources()
.getString(R.string.time_connector_next_day), " ",
noDeviceTimezoneNote);
} else {
return TextUtils.concat(spannableTime, ", ", dateFormat.format(cal.getTime()), " ",
noDeviceTimezoneNote);
}
}
}
public static CharSequence getTimeUpdated(Context applicationContext, int offsetGMT, long oldTime,
long newTime) {
Calendar calOldTime = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
Calendar calNewTime = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
DateFormat timeFormat = android.text.format.DateFormat.getTimeFormat(applicationContext);
DateFormat dateFormat = android.text.format.DateFormat.getDateFormat(applicationContext);
timeFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
CharSequence timeUpdatedString;
CharSequence beforeDateString = "", beforeTimeString = "", newDateString = "", timezone = "";
SpannableString oldTimeString, oldDateString, newTimeString;
calOldTime.setTimeInMillis(oldTime);
calNewTime.setTimeInMillis(newTime);
String noDeviceTimezoneNote = "";
if (offsetGMT != TimeZone.getDefault().getOffset(oldTime)) {
noDeviceTimezoneNote = "GMT";
if (offsetGMT != 0) {
noDeviceTimezoneNote += offsetGMT / 3600000;
}
}
calOldTime.add(Calendar.MILLISECOND, offsetGMT);
calNewTime.add(Calendar.MILLISECOND, offsetGMT);
if (ConversionUtils.isTomorrow(calNewTime)) {
oldDateString = new SpannableString(applicationContext.getResources()
.getString(R.string.time_connector_next_day) + " ");
} else {
beforeDateString = applicationContext.getResources()
.getString(R.string.time_connector_before_date) + " ";
oldDateString = new SpannableString(dateFormat.format(calNewTime.getTime()) + " ");
}
if (calNewTime.get(Calendar.DAY_OF_MONTH) != calOldTime.get(Calendar.DAY_OF_MONTH)) {
beforeDateString = applicationContext.getResources()
.getString(R.string.time_connector_before_date) + " ";
newDateString = dateFormat.format(calNewTime.getTime()) + " ";
oldDateString.setSpan(new StrikethroughSpan(), 0, oldDateString.length() - 1, 0);
}
beforeTimeString = applicationContext.getResources()
.getString(R.string.time_connector_before_time) + " ";
timezone = noDeviceTimezoneNote;
int color = applicationContext.getResources().getColor(
ArrivalInfoUtils.computeColorFromDeviation(
calNewTime.getTimeInMillis() - calOldTime.getTimeInMillis()));
newTimeString = new SpannableString(timeFormat.format(calNewTime.getTime()) + " ");
newTimeString.setSpan(new ForegroundColorSpan(color), 0, newTimeString.length(), 0);
if (calOldTime.get(Calendar.HOUR_OF_DAY) != calNewTime.get(Calendar.HOUR_OF_DAY)
|| calOldTime.get(Calendar.MINUTE) != calNewTime.get(Calendar.MINUTE)) {
oldTimeString = new SpannableString(timeFormat.format(calOldTime.getTime()) + " ");
oldTimeString.setSpan(new StrikethroughSpan(), 0, oldTimeString.length() - 1, 0);
} else {
oldTimeString = new SpannableString(" ");
}
timeUpdatedString = TextUtils.concat(beforeDateString, newDateString, oldDateString,
beforeTimeString, oldTimeString, newTimeString, timezone);
return timeUpdatedString;
}
public static boolean isToday(Calendar cal) {
Calendar actualTime = Calendar.getInstance();
return (actualTime.get(Calendar.ERA) == cal.get(Calendar.ERA) &&
actualTime.get(Calendar.YEAR) == cal.get(Calendar.YEAR) &&
actualTime.get(Calendar.DAY_OF_YEAR) == cal.get(Calendar.DAY_OF_YEAR));
}
public static boolean isTomorrow(Calendar cal) {
Calendar tomorrowTime = Calendar.getInstance();
tomorrowTime.add(Calendar.DAY_OF_YEAR, 1);
return (tomorrowTime.get(Calendar.ERA) == cal.get(Calendar.ERA) &&
tomorrowTime.get(Calendar.YEAR) == cal.get(Calendar.YEAR) &&
tomorrowTime.get(Calendar.DAY_OF_YEAR) == cal.get(Calendar.DAY_OF_YEAR));
}
/**
* Shows only the last n words of a sentence, being n the number of words to make the longer
* sentence that still is smaller than maxLength.
*
* @param sentence phrase to shrink
* @param maxLength max length of the new sentence
* @return the reduced sentence
*/
public static String tailAndTruncateSentence(String sentence, int maxLength) {
String[] words = sentence.split(" ");
List<String> list = Arrays.asList(words);
Collections.reverse(list);
String[] reversedWords = (String[]) list.toArray();
String modifiedSentence = "";
for (String word : reversedWords) {
if (modifiedSentence.length() >= maxLength) {
return modifiedSentence;
}
modifiedSentence = word + " " + modifiedSentence;
}
return modifiedSentence;
}
/**
* Always creates a correct value for the short name of the route. Using the routeShortName,
* processing the long name if the short is null or returning an empty string if both names are
* null.
*
* Route short name will be preceded by adequate connector.
*
* @param routeLongName to convert it to a route short name if necessary
* @param routeShortName to be returned if is not null
* @return a valid route short name
*/
public static String getRouteShortNameSafe(String routeShortName, String routeLongName, Context context) {
String routeName = "";
if (routeShortName != null || routeLongName != null) {
routeName += context.getResources()
.getString(R.string.connector_before_route);
if (routeShortName != null) {
routeName += " " + routeShortName;
} else if (routeLongName != null) {
routeName += " " + tailAndTruncateSentence(routeLongName, OTPConstants.ROUTE_SHORT_NAME_MAX_SIZE);
}
}
return routeName;
}
/**
* Always creates a correct value for the long name of the route. Using the routeLongName,
* returning the short name if the long is null or returning an empty string if both names are
* null.
*
* @param routeLongName to be returned if is not null
* @param routeShortName to use if necessary
* @return a valid route long name
*/
public static String getRouteLongNameSafe(String routeLongName, String routeShortName,
boolean includeShortName) {
String routeName = "";
if (routeShortName != null || routeLongName != null) {
if (routeLongName != null) {
if (includeShortName && routeShortName != null) {
routeName = routeShortName + " " + "(" + routeLongName + ")";
} else {
routeName += routeLongName;
}
} else if (routeShortName != null) {
routeName += routeShortName;
}
}
return routeName;
}
/**
* Convert meters to feet.
*
* @param meters
* @return feet
*/
public static double metersToFeet(double meters) {
return meters * FEET_PER_METER;
}
/**
* Convert feet to meters.
*
* @param feet
* @return meters
*/
public static double feetToMeters(double feet) {
return feet / FEET_PER_METER;
}
}