在日常的开发中,我们常常需要根据各种需要,对日期进行不同形式的格式化。这里我将日常用到的日期格式化加以总结。写出个工具方法,欢迎大家总结:
1、对时间的加减操作,主要需求为闹钟等题型功能,用到的API为:Calendar
/**
* 获取增加多少月的时间
*
* @return addMonth - 增加多少月
*/
public static Date getAddMonthDate(int addMonth) {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MONTH, addMonth);
return calendar.getTime();
}
/**
* 获取增加多少天的时间
*
* @return addDay - 增加多少天
*/
public static Date getAddDayDate(int addDay) {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_YEAR, addDay);
return calendar.getTime();
}
/**
* 获取增加多少小时的时间
*
* @return addDay - 增加多少消失
*/
public static Date getAddHourDate(int addHour) {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.HOUR, addHour);
return calendar.getTime();
}
/**
* 显示时间格式为 hh:mm
*
* @param context
* @param when
* @return String
*/
@SuppressLint("SimpleDateFormat")
public static String formatTimeShort(Context context, long when) {
String formatStr = "HH:mm";
SimpleDateFormat sdf = new SimpleDateFormat(formatStr);
String temp = sdf.format(when);
if (temp != null && temp.length() == 5 && temp.substring(0, 1).equals("0")) {
temp = temp.substring(1);
}
return temp;
}
3、格式时间,让其输出,今天,昨天,两天前以及日期等。
/**
* 显示时间格式为今天、昨天、yyyy/MM/dd hh:mm
*
* @param context
* @param when
* @return String
*/
public static String formatTimeString(Context context, long when) {
Time then = new Time();
then.set(when);
Time now = new Time();
now.setToNow();
String formatStr;
if (then.year != now.year) {
formatStr = "yyyy/MM/dd";
} else if (then.yearDay != now.yearDay) {
// If it is from a different day than today, show only the date.
formatStr = "MM/dd";
} else {
// Otherwise, if the message is from today, show the time.
formatStr = "HH:MM";
}
if (then.year == now.year && then.yearDay == now.yearDay) {
return context.getString(R.string.date_today);
} else if ((then.year == now.year) && ((now.yearDay - then.yearDay) == 1)) {
return context.getString(R.string.date_yesterday);
} else {
SimpleDateFormat sdf = new SimpleDateFormat(formatStr);
String temp = sdf.format(when);
if (temp != null && temp.length() == 5 && temp.substring(0, 1).equals("0")) {
temp = temp.substring(1);
}
return temp;
}
}
4、按照上下午显示时间:主要用到的API,DateUtils
DateUtils.formatDateTime(this, System.getTimeMillis(), DateUtils.FORMAT_SHOW_TIME);
5、判断两个日期是否是同一个日期:
/**
* 是否同一天
*
* @param date1
* @param date2
* @return
*/
public static boolean isSameDate(long date1, long date2) {
long days1 = date1 / (1000 * 60 * 60 * 24);
long days2 = date2 / (1000 * 60 * 60 * 24);
return days1 == days2;
}
6、附上android系统的DateUtils类的代码。
public class DateUtils
{
private static final Object sLock = new Object();
private static Configuration sLastConfig;
private static java.text.DateFormat sStatusTimeFormat;
private static String sElapsedFormatMMSS;
private static String sElapsedFormatHMMSS;
private static final String FAST_FORMAT_HMMSS = "%1$d:%2$02d:%3$02d";
private static final String FAST_FORMAT_MMSS = "%1$02d:%2$02d";
private static final char TIME_SEPARATOR = ':';
public static final long SECOND_IN_MILLIS = 1000;
public static final long MINUTE_IN_MILLIS = SECOND_IN_MILLIS * 60;
public static final long HOUR_IN_MILLIS = MINUTE_IN_MILLIS * 60;
public static final long DAY_IN_MILLIS = HOUR_IN_MILLIS * 24;
public static final long WEEK_IN_MILLIS = DAY_IN_MILLIS * 7;
/**
* This constant is actually the length of 364 days, not of a year!
*/
public static final long YEAR_IN_MILLIS = WEEK_IN_MILLIS * 52;
// The following FORMAT_* symbols are used for specifying the format of
// dates and times in the formatDateRange method.
public static final int FORMAT_SHOW_TIME = 0x00001;
public static final int FORMAT_SHOW_WEEKDAY = 0x00002;
public static final int FORMAT_SHOW_YEAR = 0x00004;
public static final int FORMAT_NO_YEAR = 0x00008;
public static final int FORMAT_SHOW_DATE = 0x00010;
public static final int FORMAT_NO_MONTH_DAY = 0x00020;
@Deprecated
public static final int FORMAT_12HOUR = 0x00040;
@Deprecated
public static final int FORMAT_24HOUR = 0x00080;
@Deprecated
public static final int FORMAT_CAP_AMPM = 0x00100;
public static final int FORMAT_NO_NOON = 0x00200;
@Deprecated
public static final int FORMAT_CAP_NOON = 0x00400;
public static final int FORMAT_NO_MIDNIGHT = 0x00800;
@Deprecated
public static final int FORMAT_CAP_MIDNIGHT = 0x01000;
/**
* @deprecated Use
* {@link #formatDateRange(Context, Formatter, long, long, int, String) formatDateRange}
* and pass in {@link Time#TIMEZONE_UTC Time.TIMEZONE_UTC} for the timeZone instead.
*/
@Deprecated
public static final int FORMAT_UTC = 0x02000;
public static final int FORMAT_ABBREV_TIME = 0x04000;
public static final int FORMAT_ABBREV_WEEKDAY = 0x08000;
public static final int FORMAT_ABBREV_MONTH = 0x10000;
public static final int FORMAT_NUMERIC_DATE = 0x20000;
public static final int FORMAT_ABBREV_RELATIVE = 0x40000;
public static final int FORMAT_ABBREV_ALL = 0x80000;
@Deprecated
public static final int FORMAT_CAP_NOON_MIDNIGHT = (FORMAT_CAP_NOON | FORMAT_CAP_MIDNIGHT);
@Deprecated
public static final int FORMAT_NO_NOON_MIDNIGHT = (FORMAT_NO_NOON | FORMAT_NO_MIDNIGHT);
// Date and time format strings that are constant and don't need to be
// translated.
/**
* This is not actually the preferred 24-hour date format in all locales.
* @deprecated use {@link java.text.SimpleDateFormat} instead.
*/
@Deprecated
public static final String HOUR_MINUTE_24 = "%H:%M";
public static final String MONTH_FORMAT = "%B";
/**
* This is not actually a useful month name in all locales.
* @deprecated use {@link java.text.SimpleDateFormat} instead.
*/
@Deprecated
public static final String ABBREV_MONTH_FORMAT = "%b";
public static final String NUMERIC_MONTH_FORMAT = "%m";
public static final String MONTH_DAY_FORMAT = "%-d";
public static final String YEAR_FORMAT = "%Y";
public static final String YEAR_FORMAT_TWO_DIGITS = "%g";
public static final String WEEKDAY_FORMAT = "%A";
public static final String ABBREV_WEEKDAY_FORMAT = "%a";
// This table is used to lookup the resource string id of a format string
// used for formatting a start and end date that fall in the same year.
// The index is constructed from a bit-wise OR of the boolean values:
// {showTime, showYear, showWeekDay}. For example, if showYear and
// showWeekDay are both true, then the index would be 3.
/** @deprecated do not use. */
public static final int sameYearTable[] = {
com.android.internal.R.string.same_year_md1_md2,
com.android.internal.R.string.same_year_wday1_md1_wday2_md2,
com.android.internal.R.string.same_year_mdy1_mdy2,
com.android.internal.R.string.same_year_wday1_mdy1_wday2_mdy2,
com.android.internal.R.string.same_year_md1_time1_md2_time2,
com.android.internal.R.string.same_year_wday1_md1_time1_wday2_md2_time2,
com.android.internal.R.string.same_year_mdy1_time1_mdy2_time2,
com.android.internal.R.string.same_year_wday1_mdy1_time1_wday2_mdy2_time2,
// Numeric date strings
com.android.internal.R.string.numeric_md1_md2,
com.android.internal.R.string.numeric_wday1_md1_wday2_md2,
com.android.internal.R.string.numeric_mdy1_mdy2,
com.android.internal.R.string.numeric_wday1_mdy1_wday2_mdy2,
com.android.internal.R.string.numeric_md1_time1_md2_time2,
com.android.internal.R.string.numeric_wday1_md1_time1_wday2_md2_time2,
com.android.internal.R.string.numeric_mdy1_time1_mdy2_time2,
com.android.internal.R.string.numeric_wday1_mdy1_time1_wday2_mdy2_time2,
};
// This table is used to lookup the resource string id of a format string
// used for formatting a start and end date that fall in the same month.
// The index is constructed from a bit-wise OR of the boolean values:
// {showTime, showYear, showWeekDay}. For example, if showYear and
// showWeekDay are both true, then the index would be 3.
/** @deprecated do not use. */
public static final int sameMonthTable[] = {
com.android.internal.R.string.same_month_md1_md2,
com.android.internal.R.string.same_month_wday1_md1_wday2_md2,
com.android.internal.R.string.same_month_mdy1_mdy2,
com.android.internal.R.string.same_month_wday1_mdy1_wday2_mdy2,
com.android.internal.R.string.same_month_md1_time1_md2_time2,
com.android.internal.R.string.same_month_wday1_md1_time1_wday2_md2_time2,
com.android.internal.R.string.same_month_mdy1_time1_mdy2_time2,
com.android.internal.R.string.same_month_wday1_mdy1_time1_wday2_mdy2_time2,
com.android.internal.R.string.numeric_md1_md2,
com.android.internal.R.string.numeric_wday1_md1_wday2_md2,
com.android.internal.R.string.numeric_mdy1_mdy2,
com.android.internal.R.string.numeric_wday1_mdy1_wday2_mdy2,
com.android.internal.R.string.numeric_md1_time1_md2_time2,
com.android.internal.R.string.numeric_wday1_md1_time1_wday2_md2_time2,
com.android.internal.R.string.numeric_mdy1_time1_mdy2_time2,
com.android.internal.R.string.numeric_wday1_mdy1_time1_wday2_mdy2_time2,
};
/**
* Request the full spelled-out name. For use with the 'abbrev' parameter of
* {@link #getDayOfWeekString} and {@link #getMonthString}.
*
* @more
* e.g. "Sunday" or "January"
* @deprecated use {@link java.text.SimpleDateFormat} instead.
*/
@Deprecated
public static final int LENGTH_LONG = 10;
/**
* Request an abbreviated version of the name. For use with the 'abbrev'
* parameter of {@link #getDayOfWeekString} and {@link #getMonthString}.
*
* @more
* e.g. "Sun" or "Jan"
* @deprecated use {@link java.text.SimpleDateFormat} instead.
*/
@Deprecated
public static final int LENGTH_MEDIUM = 20;
/**
* Request a shorter abbreviated version of the name.
* For use with the 'abbrev' parameter of {@link #getDayOfWeekString} and {@link #getMonthString}.
* @more
*
e.g. "Su" or "Jan"
*
In most languages, the results returned for LENGTH_SHORT will be the same as
* the results returned for {@link #LENGTH_MEDIUM}.
* @deprecated use {@link java.text.SimpleDateFormat} instead.
*/
@Deprecated
public static final int LENGTH_SHORT = 30;
/**
* Request an even shorter abbreviated version of the name.
* Do not use this. Currently this will always return the same result
* as {@link #LENGTH_SHORT}.
* @deprecated use {@link java.text.SimpleDateFormat} instead.
*/
@Deprecated
public static final int LENGTH_SHORTER = 40;
/**
* Request an even shorter abbreviated version of the name.
* For use with the 'abbrev' parameter of {@link #getDayOfWeekString} and {@link #getMonthString}.
* @more
*
e.g. "S", "T", "T" or "J"
*
In some languages, the results returned for LENGTH_SHORTEST will be the same as
* the results returned for {@link #LENGTH_SHORT}.
* @deprecated use {@link java.text.SimpleDateFormat} instead.
*/
@Deprecated
public static final int LENGTH_SHORTEST = 50;
/**
* Return a string for the day of the week.
* @param dayOfWeek One of {@link Calendar#SUNDAY Calendar.SUNDAY},
* {@link Calendar#MONDAY Calendar.MONDAY}, etc.
* @param abbrev One of {@link #LENGTH_LONG}, {@link #LENGTH_SHORT},
* {@link #LENGTH_MEDIUM}, or {@link #LENGTH_SHORTEST}.
* Note that in most languages, {@link #LENGTH_SHORT}
* will return the same as {@link #LENGTH_MEDIUM}.
* Undefined lengths will return {@link #LENGTH_MEDIUM}
* but may return something different in the future.
* @throws IndexOutOfBoundsException if the dayOfWeek is out of bounds.
* @deprecated use {@link java.text.SimpleDateFormat} instead.
*/
@Deprecated
public static String getDayOfWeekString(int dayOfWeek, int abbrev) {
LocaleData d = LocaleData.get(Locale.getDefault());
String[] names;
switch (abbrev) {
case LENGTH_LONG: names = d.longWeekdayNames; break;
case LENGTH_MEDIUM: names = d.shortWeekdayNames; break;
case LENGTH_SHORT: names = d.shortWeekdayNames; break; // TODO
case LENGTH_SHORTER: names = d.shortWeekdayNames; break; // TODO
case LENGTH_SHORTEST: names = d.tinyWeekdayNames; break;
default: names = d.shortWeekdayNames; break;
}
return names[dayOfWeek];
}
/**
* Return a localized string for AM or PM.
* @param ampm Either {@link Calendar#AM Calendar.AM} or {@link Calendar#PM Calendar.PM}.
* @throws IndexOutOfBoundsException if the ampm is out of bounds.
* @return Localized version of "AM" or "PM".
* @deprecated use {@link java.text.SimpleDateFormat} instead.
*/
@Deprecated
public static String getAMPMString(int ampm) {
return LocaleData.get(Locale.getDefault()).amPm[ampm - Calendar.AM];
}
/**
* Return a localized string for the month of the year.
* @param month One of {@link Calendar#JANUARY Calendar.JANUARY},
* {@link Calendar#FEBRUARY Calendar.FEBRUARY}, etc.
* @param abbrev One of {@link #LENGTH_LONG}, {@link #LENGTH_MEDIUM},
* or {@link #LENGTH_SHORTEST}.
* Undefined lengths will return {@link #LENGTH_MEDIUM}
* but may return something different in the future.
* @return Localized month of the year.
* @deprecated use {@link java.text.SimpleDateFormat} instead.
*/
@Deprecated
public static String getMonthString(int month, int abbrev) {
// Note that here we use d.shortMonthNames for MEDIUM, SHORT and SHORTER.
// This is a shortcut to not spam the translators with too many variations
// of the same string. If we find that in a language the distinction
// is necessary, we can can add more without changing this API.
LocaleData d = LocaleData.get(Locale.getDefault());
String[] names;
switch (abbrev) {
case LENGTH_LONG: names = d.longMonthNames; break;
case LENGTH_MEDIUM: names = d.shortMonthNames; break;
case LENGTH_SHORT: names = d.shortMonthNames; break;
case LENGTH_SHORTER: names = d.shortMonthNames; break;
case LENGTH_SHORTEST: names = d.tinyMonthNames; break;
default: names = d.shortMonthNames; break;
}
return names[month];
}
/**
* Return a localized string for the month of the year, for
* contexts where the month is not formatted together with
* a day of the month.
*
* @param month One of {@link Calendar#JANUARY Calendar.JANUARY},
* {@link Calendar#FEBRUARY Calendar.FEBRUARY}, etc.
* @param abbrev One of {@link #LENGTH_LONG}, {@link #LENGTH_MEDIUM},
* or {@link #LENGTH_SHORTEST}.
* Undefined lengths will return {@link #LENGTH_MEDIUM}
* but may return something different in the future.
* @return Localized month of the year.
* @hide Pending API council approval
* @deprecated use {@link java.text.SimpleDateFormat} instead.
*/
@Deprecated
public static String getStandaloneMonthString(int month, int abbrev) {
// Note that here we use d.shortMonthNames for MEDIUM, SHORT and SHORTER.
// This is a shortcut to not spam the translators with too many variations
// of the same string. If we find that in a language the distinction
// is necessary, we can can add more without changing this API.
LocaleData d = LocaleData.get(Locale.getDefault());
String[] names;
switch (abbrev) {
case LENGTH_LONG: names = d.longStandAloneMonthNames;
break;
case LENGTH_MEDIUM: names = d.shortMonthNames; break;
case LENGTH_SHORT: names = d.shortMonthNames; break;
case LENGTH_SHORTER: names = d.shortMonthNames; break;
case LENGTH_SHORTEST: names = d.tinyMonthNames; break;
default: names = d.shortMonthNames; break;
}
return names[month];
}
/**
* Returns a string describing the elapsed time since startTime.
* @param startTime some time in the past.
* @return a String object containing the elapsed time.
* @see #getRelativeTimeSpanString(long, long, long)
*/
public static CharSequence getRelativeTimeSpanString(long startTime) {
return getRelativeTimeSpanString(startTime, System.currentTimeMillis(), MINUTE_IN_MILLIS);
}
/**
* Returns a string describing 'time' as a time relative to 'now'.
*
* Time spans in the past are formatted like "42 minutes ago".
* Time spans in the future are formatted like "in 42 minutes".
*
* @param time the time to describe, in milliseconds
* @param now the current time in milliseconds
* @param minResolution the minimum timespan to report. For example, a time 3 seconds in the
* past will be reported as "0 minutes ago" if this is set to MINUTE_IN_MILLIS. Pass one of
* 0, MINUTE_IN_MILLIS, HOUR_IN_MILLIS, DAY_IN_MILLIS, WEEK_IN_MILLIS
*/
public static CharSequence getRelativeTimeSpanString(long time, long now, long minResolution) {
int flags = FORMAT_SHOW_DATE | FORMAT_SHOW_YEAR | FORMAT_ABBREV_MONTH;
return getRelativeTimeSpanString(time, now, minResolution, flags);
}
/**
* Returns a string describing 'time' as a time relative to 'now'.
*
* Time spans in the past are formatted like "42 minutes ago". Time spans in
* the future are formatted like "in 42 minutes".
*
* Can use {@link #FORMAT_ABBREV_RELATIVE} flag to use abbreviated relative
* times, like "42 mins ago".
*
* @param time the time to describe, in milliseconds
* @param now the current time in milliseconds
* @param minResolution the minimum timespan to report. For example, a time
* 3 seconds in the past will be reported as "0 minutes ago" if
* this is set to MINUTE_IN_MILLIS. Pass one of 0,
* MINUTE_IN_MILLIS, HOUR_IN_MILLIS, DAY_IN_MILLIS,
* WEEK_IN_MILLIS
* @param flags a bit mask of formatting options, such as
* {@link #FORMAT_NUMERIC_DATE} or
* {@link #FORMAT_ABBREV_RELATIVE}
*/
public static CharSequence getRelativeTimeSpanString(long time, long now, long minResolution,
int flags) {
Resources r = Resources.getSystem();
boolean abbrevRelative = (flags & (FORMAT_ABBREV_RELATIVE | FORMAT_ABBREV_ALL)) != 0;
boolean past = (now >= time);
long duration = Math.abs(now - time);
int resId;
long count;
if (duration < MINUTE_IN_MILLIS && minResolution < MINUTE_IN_MILLIS) {
count = duration / SECOND_IN_MILLIS;
if (past) {
if (abbrevRelative) {
resId = com.android.internal.R.plurals.abbrev_num_seconds_ago;
} else {
resId = com.android.internal.R.plurals.num_seconds_ago;
}
} else {
if (abbrevRelative) {
resId = com.android.internal.R.plurals.abbrev_in_num_seconds;
} else {
resId = com.android.internal.R.plurals.in_num_seconds;
}
}
} else if (duration < HOUR_IN_MILLIS && minResolution < HOUR_IN_MILLIS) {
count = duration / MINUTE_IN_MILLIS;
if (past) {
if (abbrevRelative) {
resId = com.android.internal.R.plurals.abbrev_num_minutes_ago;
} else {
resId = com.android.internal.R.plurals.num_minutes_ago;
}
} else {
if (abbrevRelative) {
resId = com.android.internal.R.plurals.abbrev_in_num_minutes;
} else {
resId = com.android.internal.R.plurals.in_num_minutes;
}
}
} else if (duration < DAY_IN_MILLIS && minResolution < DAY_IN_MILLIS) {
count = duration / HOUR_IN_MILLIS;
if (past) {
if (abbrevRelative) {
resId = com.android.internal.R.plurals.abbrev_num_hours_ago;
} else {
resId = com.android.internal.R.plurals.num_hours_ago;
}
} else {
if (abbrevRelative) {
resId = com.android.internal.R.plurals.abbrev_in_num_hours;
} else {
resId = com.android.internal.R.plurals.in_num_hours;
}
}
} else if (duration < WEEK_IN_MILLIS && minResolution < WEEK_IN_MILLIS) {
count = getNumberOfDaysPassed(time, now);
if (past) {
if (abbrevRelative) {
resId = com.android.internal.R.plurals.abbrev_num_days_ago;
} else {
resId = com.android.internal.R.plurals.num_days_ago;
}
} else {
if (abbrevRelative) {
resId = com.android.internal.R.plurals.abbrev_in_num_days;
} else {
resId = com.android.internal.R.plurals.in_num_days;
}
}
} else {
// We know that we won't be showing the time, so it is safe to pass
// in a null context.
return formatDateRange(null, time, time, flags);
}
String format = r.getQuantityString(resId, (int) count);
return String.format(format, count);
}
/**
* Returns the number of days passed between two dates.
*
* @param date1 first date
* @param date2 second date
* @return number of days passed between to dates.
*/
private synchronized static long getNumberOfDaysPassed(long date1, long date2) {
if (sThenTime == null) {
sThenTime = new Time();
}
sThenTime.set(date1);
int day1 = Time.getJulianDay(date1, sThenTime.gmtoff);
sThenTime.set(date2);
int day2 = Time.getJulianDay(date2, sThenTime.gmtoff);
return Math.abs(day2 - day1);
}
/**
* Return string describing the elapsed time since startTime formatted like
* "[relative time/date], [time]".
*
* Example output strings for the US date format.
*
* - 3 mins ago, 10:15 AM
* - yesterday, 12:20 PM
* - Dec 12, 4:12 AM
* - 11/14/2007, 8:20 AM
*
*
* @param time some time in the past.
* @param minResolution the minimum elapsed time (in milliseconds) to report
* when showing relative times. For example, a time 3 seconds in
* the past will be reported as "0 minutes ago" if this is set to
* {@link #MINUTE_IN_MILLIS}.
* @param transitionResolution the elapsed time (in milliseconds) at which
* to stop reporting relative measurements. Elapsed times greater
* than this resolution will default to normal date formatting.
* For example, will transition from "6 days ago" to "Dec 12"
* when using {@link #WEEK_IN_MILLIS}.
*/
public static CharSequence getRelativeDateTimeString(Context c, long time, long minResolution,
long transitionResolution, int flags) {
Resources r = Resources.getSystem();
long now = System.currentTimeMillis();
long duration = Math.abs(now - time);
// getRelativeTimeSpanString() doesn't correctly format relative dates
// above a week or exact dates below a day, so clamp
// transitionResolution as needed.
if (transitionResolution > WEEK_IN_MILLIS) {
transitionResolution = WEEK_IN_MILLIS;
} else if (transitionResolution < DAY_IN_MILLIS) {
transitionResolution = DAY_IN_MILLIS;
}
CharSequence timeClause = formatDateRange(c, time, time, FORMAT_SHOW_TIME);
String result;
if (duration < transitionResolution) {
CharSequence relativeClause = getRelativeTimeSpanString(time, now, minResolution, flags);
result = r.getString(com.android.internal.R.string.relative_time, relativeClause, timeClause);
} else {
CharSequence dateClause = getRelativeTimeSpanString(c, time, false);
result = r.getString(com.android.internal.R.string.date_time, dateClause, timeClause);
}
return result;
}
/**
* Returns a string describing a day relative to the current day. For example if the day is
* today this function returns "Today", if the day was a week ago it returns "7 days ago", and
* if the day is in 2 weeks it returns "in 14 days".
*
* @param r the resources to get the strings from
* @param day the relative day to describe in UTC milliseconds
* @param today the current time in UTC milliseconds
* @return a formatting string
*/
private static final String getRelativeDayString(Resources r, long day, long today) {
Time startTime = new Time();
startTime.set(day);
Time currentTime = new Time();
currentTime.set(today);
int startDay = Time.getJulianDay(day, startTime.gmtoff);
int currentDay = Time.getJulianDay(today, currentTime.gmtoff);
int days = Math.abs(currentDay - startDay);
boolean past = (today > day);
// TODO: some locales name other days too, such as de_DE's "Vorgestern" (today - 2).
Locale locale = r.getConfiguration().locale;
if (locale == null) {
locale = Locale.getDefault();
}
if (days == 1) {
if (past) {
return LocaleData.get(locale).yesterday;
} else {
return LocaleData.get(locale).tomorrow;
}
} else if (days == 0) {
return LocaleData.get(locale).today;
}
int resId;
if (past) {
resId = com.android.internal.R.plurals.num_days_ago;
} else {
resId = com.android.internal.R.plurals.in_num_days;
}
String format = r.getQuantityString(resId, days);
return String.format(format, days);
}
private static void initFormatStrings() {
synchronized (sLock) {
initFormatStringsLocked();
}
}
private static void initFormatStringsLocked() {
Resources r = Resources.getSystem();
Configuration cfg = r.getConfiguration();
if (sLastConfig == null || !sLastConfig.equals(cfg)) {
sLastConfig = cfg;
sStatusTimeFormat = java.text.DateFormat.getTimeInstance(java.text.DateFormat.SHORT);
sElapsedFormatMMSS = r.getString(com.android.internal.R.string.elapsed_time_short_format_mm_ss);
sElapsedFormatHMMSS = r.getString(com.android.internal.R.string.elapsed_time_short_format_h_mm_ss);
}
}
/**
* Format a time so it appears like it would in the status bar clock.
* @deprecated use {@link #DateFormat.getTimeFormat(Context)} instead.
* @hide
*/
public static final CharSequence timeString(long millis) {
synchronized (sLock) {
initFormatStringsLocked();
return sStatusTimeFormat.format(millis);
}
}
/**
* Formats an elapsed time in the form "MM:SS" or "H:MM:SS"
* for display on the call-in-progress screen.
* @param elapsedSeconds the elapsed time in seconds.
*/
public static String formatElapsedTime(long elapsedSeconds) {
return formatElapsedTime(null, elapsedSeconds);
}
/**
* Formats an elapsed time in the form "MM:SS" or "H:MM:SS"
* for display on the call-in-progress screen.
*
* @param recycle {@link StringBuilder} to recycle, if possible
* @param elapsedSeconds the elapsed time in seconds.
*/
public static String formatElapsedTime(StringBuilder recycle, long elapsedSeconds) {
initFormatStrings();
long hours = 0;
long minutes = 0;
long seconds = 0;
if (elapsedSeconds >= 3600) {
hours = elapsedSeconds / 3600;
elapsedSeconds -= hours * 3600;
}
if (elapsedSeconds >= 60) {
minutes = elapsedSeconds / 60;
elapsedSeconds -= minutes * 60;
}
seconds = elapsedSeconds;
String result;
if (hours > 0) {
return formatElapsedTime(recycle, sElapsedFormatHMMSS, hours, minutes, seconds);
} else {
return formatElapsedTime(recycle, sElapsedFormatMMSS, minutes, seconds);
}
}
private static void append(StringBuilder sb, long value, boolean pad, char zeroDigit) {
if (value < 10) {
if (pad) {
sb.append(zeroDigit);
}
} else {
sb.append((char) (zeroDigit + (value / 10)));
}
sb.append((char) (zeroDigit + (value % 10)));
}
/**
* Fast formatting of h:mm:ss.
*/
private static String formatElapsedTime(StringBuilder recycle, String format, long hours,
long minutes, long seconds) {
if (FAST_FORMAT_HMMSS.equals(format)) {
char zeroDigit = LocaleData.get(Locale.getDefault()).zeroDigit;
StringBuilder sb = recycle;
if (sb == null) {
sb = new StringBuilder(8);
} else {
sb.setLength(0);
}
append(sb, hours, false, zeroDigit);
sb.append(TIME_SEPARATOR);
append(sb, minutes, true, zeroDigit);
sb.append(TIME_SEPARATOR);
append(sb, seconds, true, zeroDigit);
return sb.toString();
} else {
return String.format(format, hours, minutes, seconds);
}
}
/**
* Fast formatting of mm:ss.
*/
private static String formatElapsedTime(StringBuilder recycle, String format, long minutes,
long seconds) {
if (FAST_FORMAT_MMSS.equals(format)) {
char zeroDigit = LocaleData.get(Locale.getDefault()).zeroDigit;
StringBuilder sb = recycle;
if (sb == null) {
sb = new StringBuilder(8);
} else {
sb.setLength(0);
}
append(sb, minutes, false, zeroDigit);
sb.append(TIME_SEPARATOR);
append(sb, seconds, true, zeroDigit);
return sb.toString();
} else {
return String.format(format, minutes, seconds);
}
}
/**
* Format a date / time such that if the then is on the same day as now, it shows
* just the time and if it's a different day, it shows just the date.
*
* The parameters dateFormat and timeFormat should each be one of
* {@link java.text.DateFormat#DEFAULT},
* {@link java.text.DateFormat#FULL},
* {@link java.text.DateFormat#LONG},
* {@link java.text.DateFormat#MEDIUM}
* or
* {@link java.text.DateFormat#SHORT}
*
* @param then the date to format
* @param now the base time
* @param dateStyle how to format the date portion.
* @param timeStyle how to format the time portion.
*/
public static final CharSequence formatSameDayTime(long then, long now,
int dateStyle, int timeStyle) {
Calendar thenCal = new GregorianCalendar();
thenCal.setTimeInMillis(then);
Date thenDate = thenCal.getTime();
Calendar nowCal = new GregorianCalendar();
nowCal.setTimeInMillis(now);
java.text.DateFormat f;
if (thenCal.get(Calendar.YEAR) == nowCal.get(Calendar.YEAR)
&& thenCal.get(Calendar.MONTH) == nowCal.get(Calendar.MONTH)
&& thenCal.get(Calendar.DAY_OF_MONTH) == nowCal.get(Calendar.DAY_OF_MONTH)) {
f = java.text.DateFormat.getTimeInstance(timeStyle);
} else {
f = java.text.DateFormat.getDateInstance(dateStyle);
}
return f.format(thenDate);
}
/**
* @hide
* @deprecated use {@link android.text.format.Time}
*/
public static Calendar newCalendar(boolean zulu)
{
if (zulu)
return Calendar.getInstance(TimeZone.getTimeZone("GMT"));
return Calendar.getInstance();
}
/**
* @return true if the supplied when is today else false
*/
public static boolean isToday(long when) {
Time time = new Time();
time.set(when);
int thenYear = time.year;
int thenMonth = time.month;
int thenMonthDay = time.monthDay;
time.set(System.currentTimeMillis());
return (thenYear == time.year)
&& (thenMonth == time.month)
&& (thenMonthDay == time.monthDay);
}
/**
* @hide
* @deprecated use {@link android.text.format.Time}
* Return true if this date string is local time
*/
public static boolean isUTC(String s)
{
if (s.length() == 16 && s.charAt(15) == 'Z') {
return true;
}
if (s.length() == 9 && s.charAt(8) == 'Z') {
// XXX not sure if this case possible/valid
return true;
}
return false;
}
/**
* Return a string containing the date and time in RFC2445 format.
* Ensures that the time is written in UTC. The Calendar class doesn't
* really help out with this, so this is slower than it ought to be.
*
* @param cal the date and time to write
* @hide
* @deprecated use {@link android.text.format.Time}
*/
public static String writeDateTime(Calendar cal)
{
TimeZone tz = TimeZone.getTimeZone("GMT");
GregorianCalendar c = new GregorianCalendar(tz);
c.setTimeInMillis(cal.getTimeInMillis());
return writeDateTime(c, true);
}
/**
* Return a string containing the date and time in RFC2445 format.
*
* @param cal the date and time to write
* @param zulu If the calendar is in UTC, pass true, and a Z will
* be written at the end as per RFC2445. Otherwise, the time is
* considered in localtime.
* @hide
* @deprecated use {@link android.text.format.Time}
*/
public static String writeDateTime(Calendar cal, boolean zulu)
{
StringBuilder sb = new StringBuilder();
sb.ensureCapacity(16);
if (zulu) {
sb.setLength(16);
sb.setCharAt(15, 'Z');
} else {
sb.setLength(15);
}
return writeDateTime(cal, sb);
}
/**
* Return a string containing the date and time in RFC2445 format.
*
* @param cal the date and time to write
* @param sb a StringBuilder to use. It is assumed that setLength
* has already been called on sb to the appropriate length
* which is sb.setLength(zulu ? 16 : 15)
* @hide
* @deprecated use {@link android.text.format.Time}
*/
public static String writeDateTime(Calendar cal, StringBuilder sb)
{
int n;
n = cal.get(Calendar.YEAR);
sb.setCharAt(3, (char)('0'+n%10));
n /= 10;
sb.setCharAt(2, (char)('0'+n%10));
n /= 10;
sb.setCharAt(1, (char)('0'+n%10));
n /= 10;
sb.setCharAt(0, (char)('0'+n%10));
n = cal.get(Calendar.MONTH) + 1;
sb.setCharAt(5, (char)('0'+n%10));
n /= 10;
sb.setCharAt(4, (char)('0'+n%10));
n = cal.get(Calendar.DAY_OF_MONTH);
sb.setCharAt(7, (char)('0'+n%10));
n /= 10;
sb.setCharAt(6, (char)('0'+n%10));
sb.setCharAt(8, 'T');
n = cal.get(Calendar.HOUR_OF_DAY);
sb.setCharAt(10, (char)('0'+n%10));
n /= 10;
sb.setCharAt(9, (char)('0'+n%10));
n = cal.get(Calendar.MINUTE);
sb.setCharAt(12, (char)('0'+n%10));
n /= 10;
sb.setCharAt(11, (char)('0'+n%10));
n = cal.get(Calendar.SECOND);
sb.setCharAt(14, (char)('0'+n%10));
n /= 10;
sb.setCharAt(13, (char)('0'+n%10));
return sb.toString();
}
/**
* @hide
* @deprecated use {@link android.text.format.Time}
*/
public static void assign(Calendar lval, Calendar rval)
{
// there should be a faster way.
lval.clear();
lval.setTimeInMillis(rval.getTimeInMillis());
}
/**
* Formats a date or a time range according to the local conventions.
*
* Note that this is a convenience method. Using it involves creating an
* internal {@link java.util.Formatter} instance on-the-fly, which is
* somewhat costly in terms of memory and time. This is probably acceptable
* if you use the method only rarely, but if you rely on it for formatting a
* large number of dates, consider creating and reusing your own
* {@link java.util.Formatter} instance and use the version of
* {@link #formatDateRange(Context, long, long, int) formatDateRange}
* that takes a {@link java.util.Formatter}.
*
* @param context the context is required only if the time is shown
* @param startMillis the start time in UTC milliseconds
* @param endMillis the end time in UTC milliseconds
* @param flags a bit mask of options See
* {@link #formatDateRange(Context, Formatter, long, long, int, String) formatDateRange}
* @return a string containing the formatted date/time range.
*/
public static String formatDateRange(Context context, long startMillis,
long endMillis, int flags) {
Formatter f = new Formatter(new StringBuilder(50), Locale.getDefault());
return formatDateRange(context, f, startMillis, endMillis, flags).toString();
}
/**
* Formats a date or a time range according to the local conventions.
*
* Note that this is a convenience method for formatting the date or
* time range in the local time zone. If you want to specify the time
* zone please use
* {@link #formatDateRange(Context, Formatter, long, long, int, String) formatDateRange}.
*
* @param context the context is required only if the time is shown
* @param formatter the Formatter used for formatting the date range.
* Note: be sure to call setLength(0) on StringBuilder passed to
* the Formatter constructor unless you want the results to accumulate.
* @param startMillis the start time in UTC milliseconds
* @param endMillis the end time in UTC milliseconds
* @param flags a bit mask of options See
* {@link #formatDateRange(Context, Formatter, long, long, int, String) formatDateRange}
* @return a string containing the formatted date/time range.
*/
public static Formatter formatDateRange(Context context, Formatter formatter, long startMillis,
long endMillis, int flags) {
return formatDateRange(context, formatter, startMillis, endMillis, flags, null);
}
/**
* Formats a date or a time range according to the local conventions.
*
*
* Example output strings (date formats in these examples are shown using
* the US date format convention but that may change depending on the
* local settings):
*
* - 10:15am
* - 3:00pm - 4:00pm
* - 3pm - 4pm
* - 3PM - 4PM
* - 08:00 - 17:00
* - Oct 9
* - Tue, Oct 9
* - October 9, 2007
* - Oct 9 - 10
* - Oct 9 - 10, 2007
* - Oct 28 - Nov 3, 2007
* - Dec 31, 2007 - Jan 1, 2008
* - Oct 9, 8:00am - Oct 10, 5:00pm
* - 12/31/2007 - 01/01/2008
*
*
*
* The flags argument is a bitmask of options from the following list:
*
*
* - FORMAT_SHOW_TIME
* - FORMAT_SHOW_WEEKDAY
* - FORMAT_SHOW_YEAR
* - FORMAT_NO_YEAR
* - FORMAT_SHOW_DATE
* - FORMAT_NO_MONTH_DAY
* - FORMAT_12HOUR
* - FORMAT_24HOUR
* - FORMAT_CAP_AMPM
* - FORMAT_NO_NOON
* - FORMAT_CAP_NOON
* - FORMAT_NO_MIDNIGHT
* - FORMAT_CAP_MIDNIGHT
* - FORMAT_UTC
* - FORMAT_ABBREV_TIME
* - FORMAT_ABBREV_WEEKDAY
* - FORMAT_ABBREV_MONTH
* - FORMAT_ABBREV_ALL
* - FORMAT_NUMERIC_DATE
*
*
*
* If FORMAT_SHOW_TIME is set, the time is shown as part of the date range.
* If the start and end time are the same, then just the start time is
* shown.
*
*
* If FORMAT_SHOW_WEEKDAY is set, then the weekday is shown.
*
*
* If FORMAT_SHOW_YEAR is set, then the year is always shown.
* If FORMAT_NO_YEAR is set, then the year is not shown.
* If neither FORMAT_SHOW_YEAR nor FORMAT_NO_YEAR are set, then the year
* is shown only if it is different from the current year, or if the start
* and end dates fall on different years. If both are set,
* FORMAT_SHOW_YEAR takes precedence.
*
*
* Normally the date is shown unless the start and end day are the same.
* If FORMAT_SHOW_DATE is set, then the date is always shown, even for
* same day ranges.
*
*
* If FORMAT_NO_MONTH_DAY is set, then if the date is shown, just the
* month name will be shown, not the day of the month. For example,
* "January, 2008" instead of "January 6 - 12, 2008".
*
*
* If FORMAT_CAP_AMPM is set and 12-hour time is used, then the "AM"
* and "PM" are capitalized. You should not use this flag
* because in some locales these terms cannot be capitalized, and in
* many others it doesn't make sense to do so even though it is possible.
*
*
* If FORMAT_NO_NOON is set and 12-hour time is used, then "12pm" is
* shown instead of "noon".
*
*
* If FORMAT_CAP_NOON is set and 12-hour time is used, then "Noon" is
* shown instead of "noon". You should probably not use this flag
* because in many locales it will not make sense to capitalize
* the term.
*
*
* If FORMAT_NO_MIDNIGHT is set and 12-hour time is used, then "12am" is
* shown instead of "midnight".
*
*
* If FORMAT_CAP_MIDNIGHT is set and 12-hour time is used, then "Midnight"
* is shown instead of "midnight". You should probably not use this
* flag because in many locales it will not make sense to capitalize
* the term.
*
*
* If FORMAT_12HOUR is set and the time is shown, then the time is
* shown in the 12-hour time format. You should not normally set this.
* Instead, let the time format be chosen automatically according to the
* system settings. If both FORMAT_12HOUR and FORMAT_24HOUR are set, then
* FORMAT_24HOUR takes precedence.
*
*
* If FORMAT_24HOUR is set and the time is shown, then the time is
* shown in the 24-hour time format. You should not normally set this.
* Instead, let the time format be chosen automatically according to the
* system settings. If both FORMAT_12HOUR and FORMAT_24HOUR are set, then
* FORMAT_24HOUR takes precedence.
*
*
* If FORMAT_UTC is set, then the UTC time zone is used for the start
* and end milliseconds unless a time zone is specified. If a time zone
* is specified it will be used regardless of the FORMAT_UTC flag.
*
*
* If FORMAT_ABBREV_TIME is set and 12-hour time format is used, then the
* start and end times (if shown) are abbreviated by not showing the minutes
* if they are zero. For example, instead of "3:00pm" the time would be
* abbreviated to "3pm".
*
*
* If FORMAT_ABBREV_WEEKDAY is set, then the weekday (if shown) is
* abbreviated to a 3-letter string.
*
*
* If FORMAT_ABBREV_MONTH is set, then the month (if shown) is abbreviated
* to a 3-letter string.
*
*
* If FORMAT_ABBREV_ALL is set, then the weekday and the month (if shown)
* are abbreviated to 3-letter strings.
*
*
* If FORMAT_NUMERIC_DATE is set, then the date is shown in numeric format
* instead of using the name of the month. For example, "12/31/2008"
* instead of "December 31, 2008".
*
*
* If the end date ends at 12:00am at the beginning of a day, it is
* formatted as the end of the previous day in two scenarios:
*
* - For single day events. This results in "8pm - midnight" instead of
* "Nov 10, 8pm - Nov 11, 12am".
* - When the time is not displayed. This results in "Nov 10 - 11" for
* an event with a start date of Nov 10 and an end date of Nov 12 at
* 00:00.
*
*
* @param context the context is required only if the time is shown
* @param formatter the Formatter used for formatting the date range.
* Note: be sure to call setLength(0) on StringBuilder passed to
* the Formatter constructor unless you want the results to accumulate.
* @param startMillis the start time in UTC milliseconds
* @param endMillis the end time in UTC milliseconds
* @param flags a bit mask of options
* @param timeZone the time zone to compute the string in. Use null for local
* or if the FORMAT_UTC flag is being used.
*
* @return the formatter with the formatted date/time range appended to the string buffer.
*/
public static Formatter formatDateRange(Context context, Formatter formatter, long startMillis,
long endMillis, int flags, String timeZone) {
Resources res = Resources.getSystem();
boolean showTime = (flags & FORMAT_SHOW_TIME) != 0;
boolean showWeekDay = (flags & FORMAT_SHOW_WEEKDAY) != 0;
boolean showYear = (flags & FORMAT_SHOW_YEAR) != 0;
boolean noYear = (flags & FORMAT_NO_YEAR) != 0;
boolean useUTC = (flags & FORMAT_UTC) != 0;
boolean abbrevWeekDay = (flags & (FORMAT_ABBREV_WEEKDAY | FORMAT_ABBREV_ALL)) != 0;
boolean abbrevMonth = (flags & (FORMAT_ABBREV_MONTH | FORMAT_ABBREV_ALL)) != 0;
boolean noMonthDay = (flags & FORMAT_NO_MONTH_DAY) != 0;
boolean numericDate = (flags & FORMAT_NUMERIC_DATE) != 0;
// If we're getting called with a single instant in time (from
// e.g. formatDateTime(), below), then we can skip a lot of
// computation below that'd otherwise be thrown out.
boolean isInstant = (startMillis == endMillis);
Time startDate;
if (timeZone != null) {
startDate = new Time(timeZone);
} else if (useUTC) {
startDate = new Time(Time.TIMEZONE_UTC);
} else {
startDate = new Time();
}
startDate.set(startMillis);
Time endDate;
int dayDistance;
if (isInstant) {
endDate = startDate;
dayDistance = 0;
} else {
if (timeZone != null) {
endDate = new Time(timeZone);
} else if (useUTC) {
endDate = new Time(Time.TIMEZONE_UTC);
} else {
endDate = new Time();
}
endDate.set(endMillis);
int startJulianDay = Time.getJulianDay(startMillis, startDate.gmtoff);
int endJulianDay = Time.getJulianDay(endMillis, endDate.gmtoff);
dayDistance = endJulianDay - startJulianDay;
}
if (!isInstant
&& (endDate.hour | endDate.minute | endDate.second) == 0
&& (!showTime || dayDistance <= 1)) {
endDate.monthDay -= 1;
endDate.normalize(true /* ignore isDst */);
}
int startDay = startDate.monthDay;
int startMonthNum = startDate.month;
int startYear = startDate.year;
int endDay = endDate.monthDay;
int endMonthNum = endDate.month;
int endYear = endDate.year;
String startWeekDayString = "";
String endWeekDayString = "";
if (showWeekDay) {
String weekDayFormat = "";
if (abbrevWeekDay) {
weekDayFormat = ABBREV_WEEKDAY_FORMAT;
} else {
weekDayFormat = WEEKDAY_FORMAT;
}
startWeekDayString = startDate.format(weekDayFormat);
endWeekDayString = isInstant ? startWeekDayString : endDate.format(weekDayFormat);
}
String startTimeString = "";
String endTimeString = "";
if (showTime) {
String startTimeFormat = "";
String endTimeFormat = "";
boolean force24Hour = (flags & FORMAT_24HOUR) != 0;
boolean force12Hour = (flags & FORMAT_12HOUR) != 0;
boolean use24Hour;
if (force24Hour) {
use24Hour = true;
} else if (force12Hour) {
use24Hour = false;
} else {
use24Hour = DateFormat.is24HourFormat(context);
}
if (use24Hour) {
startTimeFormat = endTimeFormat =
res.getString(com.android.internal.R.string.hour_minute_24);
} else {
boolean abbrevTime = (flags & (FORMAT_ABBREV_TIME | FORMAT_ABBREV_ALL)) != 0;
boolean capAMPM = (flags & FORMAT_CAP_AMPM) != 0;
boolean noNoon = (flags & FORMAT_NO_NOON) != 0;
boolean capNoon = (flags & FORMAT_CAP_NOON) != 0;
boolean noMidnight = (flags & FORMAT_NO_MIDNIGHT) != 0;
boolean capMidnight = (flags & FORMAT_CAP_MIDNIGHT) != 0;
boolean startOnTheHour = startDate.minute == 0 && startDate.second == 0;
boolean endOnTheHour = endDate.minute == 0 && endDate.second == 0;
if (abbrevTime && startOnTheHour) {
if (capAMPM) {
startTimeFormat = res.getString(com.android.internal.R.string.hour_cap_ampm);
} else {
startTimeFormat = res.getString(com.android.internal.R.string.hour_ampm);
}
} else {
if (capAMPM) {
startTimeFormat = res.getString(com.android.internal.R.string.hour_minute_cap_ampm);
} else {
startTimeFormat = res.getString(com.android.internal.R.string.hour_minute_ampm);
}
}
// Don't waste time on setting endTimeFormat when
// we're dealing with an instant, where we'll never
// need the end point. (It's the same as the start
// point)
if (!isInstant) {
if (abbrevTime && endOnTheHour) {
if (capAMPM) {
endTimeFormat = res.getString(com.android.internal.R.string.hour_cap_ampm);
} else {
endTimeFormat = res.getString(com.android.internal.R.string.hour_ampm);
}
} else {
if (capAMPM) {
endTimeFormat = res.getString(com.android.internal.R.string.hour_minute_cap_ampm);
} else {
endTimeFormat = res.getString(com.android.internal.R.string.hour_minute_ampm);
}
}
if (endDate.hour == 12 && endOnTheHour && !noNoon) {
if (capNoon) {
endTimeFormat = res.getString(com.android.internal.R.string.Noon);
} else {
endTimeFormat = res.getString(com.android.internal.R.string.noon);
}
} else if (endDate.hour == 0 && endOnTheHour && !noMidnight) {
if (capMidnight) {
endTimeFormat = res.getString(com.android.internal.R.string.Midnight);
} else {
endTimeFormat = res.getString(com.android.internal.R.string.midnight);
}
}
}
if (startDate.hour == 12 && startOnTheHour && !noNoon) {
if (capNoon) {
startTimeFormat = res.getString(com.android.internal.R.string.Noon);
} else {
startTimeFormat = res.getString(com.android.internal.R.string.noon);
}
// Don't show the start time starting at midnight. Show
// 12am instead.
}
}
startTimeString = startDate.format(startTimeFormat);
endTimeString = isInstant ? startTimeString : endDate.format(endTimeFormat);
}
// Show the year if the user specified FORMAT_SHOW_YEAR or if
// the starting and end years are different from each other
// or from the current year. But don't show the year if the
// user specified FORMAT_NO_YEAR.
if (showYear) {
// No code... just a comment for clarity. Keep showYear
// on, as they enabled it with FORMAT_SHOW_YEAR. This
// takes precedence over them setting FORMAT_NO_YEAR.
} else if (noYear) {
// They explicitly didn't want a year.
showYear = false;
} else if (startYear != endYear) {
showYear = true;
} else {
// Show the year if it's not equal to the current year.
Time currentTime = new Time();
currentTime.setToNow();
showYear = startYear != currentTime.year;
}
String defaultDateFormat, fullFormat, dateRange;
if (numericDate) {
defaultDateFormat = res.getString(com.android.internal.R.string.numeric_date);
} else if (showYear) {
if (abbrevMonth) {
if (noMonthDay) {
defaultDateFormat = res.getString(com.android.internal.R.string.abbrev_month_year);
} else {
defaultDateFormat = res.getString(com.android.internal.R.string.abbrev_month_day_year);
}
} else {
if (noMonthDay) {
defaultDateFormat = res.getString(com.android.internal.R.string.month_year);
} else {
defaultDateFormat = res.getString(com.android.internal.R.string.month_day_year);
}
}
} else {
if (abbrevMonth) {
if (noMonthDay) {
defaultDateFormat = res.getString(com.android.internal.R.string.abbrev_month);
} else {
defaultDateFormat = res.getString(com.android.internal.R.string.abbrev_month_day);
}
} else {
if (noMonthDay) {
defaultDateFormat = res.getString(com.android.internal.R.string.month);
} else {
defaultDateFormat = res.getString(com.android.internal.R.string.month_day);
}
}
}
if (showWeekDay) {
if (showTime) {
fullFormat = res.getString(com.android.internal.R.string.wday1_date1_time1_wday2_date2_time2);
} else {
fullFormat = res.getString(com.android.internal.R.string.wday1_date1_wday2_date2);
}
} else {
if (showTime) {
fullFormat = res.getString(com.android.internal.R.string.date1_time1_date2_time2);
} else {
fullFormat = res.getString(com.android.internal.R.string.date1_date2);
}
}
if (noMonthDay && startMonthNum == endMonthNum && startYear == endYear) {
// Example: "January, 2008"
return formatter.format("%s", startDate.format(defaultDateFormat));
}
if (startYear != endYear || noMonthDay) {
// Different year or we are not showing the month day number.
// Example: "December 31, 2007 - January 1, 2008"
// Or: "January - February, 2008"
String startDateString = startDate.format(defaultDateFormat);
String endDateString = endDate.format(defaultDateFormat);
// The values that are used in a fullFormat string are specified
// by position.
return formatter.format(fullFormat,
startWeekDayString, startDateString, startTimeString,
endWeekDayString, endDateString, endTimeString);
}
// Get the month, day, and year strings for the start and end dates
String monthFormat;
if (numericDate) {
monthFormat = NUMERIC_MONTH_FORMAT;
} else if (abbrevMonth) {
monthFormat =
res.getString(com.android.internal.R.string.short_format_month);
} else {
monthFormat = MONTH_FORMAT;
}
String startMonthString = startDate.format(monthFormat);
String startMonthDayString = startDate.format(MONTH_DAY_FORMAT);
String startYearString = startDate.format(YEAR_FORMAT);
String endMonthString = isInstant ? null : endDate.format(monthFormat);
String endMonthDayString = isInstant ? null : endDate.format(MONTH_DAY_FORMAT);
String endYearString = isInstant ? null : endDate.format(YEAR_FORMAT);
String startStandaloneMonthString = startMonthString;
String endStandaloneMonthString = endMonthString;
// We need standalone months for these strings in Persian (fa): http://b/6811327
if (!numericDate && !abbrevMonth && Locale.getDefault().getLanguage().equals("fa")) {
startStandaloneMonthString = startDate.format("%-B");
endStandaloneMonthString = endDate.format("%-B");
}
if (startMonthNum != endMonthNum) {
// Same year, different month.
// Example: "October 28 - November 3"
// or: "Wed, Oct 31 - Sat, Nov 3, 2007"
// or: "Oct 31, 8am - Sat, Nov 3, 2007, 5pm"
int index = 0;
if (showWeekDay) index = 1;
if (showYear) index += 2;
if (showTime) index += 4;
if (numericDate) index += 8;
int resId = sameYearTable[index];
fullFormat = res.getString(resId);
// The values that are used in a fullFormat string are specified
// by position.
return formatter.format(fullFormat,
startWeekDayString, startMonthString, startMonthDayString,
startYearString, startTimeString,
endWeekDayString, endMonthString, endMonthDayString,
endYearString, endTimeString,
startStandaloneMonthString, endStandaloneMonthString);
}
if (startDay != endDay) {
// Same month, different day.
int index = 0;
if (showWeekDay) index = 1;
if (showYear) index += 2;
if (showTime) index += 4;
if (numericDate) index += 8;
int resId = sameMonthTable[index];
fullFormat = res.getString(resId);
// The values that are used in a fullFormat string are specified
// by position.
return formatter.format(fullFormat,
startWeekDayString, startMonthString, startMonthDayString,
startYearString, startTimeString,
endWeekDayString, endMonthString, endMonthDayString,
endYearString, endTimeString,
startStandaloneMonthString, endStandaloneMonthString);
}
// Same start and end day
boolean showDate = (flags & FORMAT_SHOW_DATE) != 0;
// If nothing was specified, then show the date.
if (!showTime && !showDate && !showWeekDay) showDate = true;
// Compute the time string (example: "10:00 - 11:00 am")
String timeString = "";
if (showTime) {
// If the start and end time are the same, then just show the
// start time.
if (isInstant) {
// Same start and end time.
// Example: "10:15 AM"
timeString = startTimeString;
} else {
// Example: "10:00 - 11:00 am"
String timeFormat = res.getString(com.android.internal.R.string.time1_time2);
// Don't use the user supplied Formatter because the result will pollute the buffer.
timeString = String.format(timeFormat, startTimeString, endTimeString);
}
}
// Figure out which full format to use.
fullFormat = "";
String dateString = "";
if (showDate) {
dateString = startDate.format(defaultDateFormat);
if (showWeekDay) {
if (showTime) {
// Example: "10:00 - 11:00 am, Tue, Oct 9"
fullFormat = res.getString(com.android.internal.R.string.time_wday_date);
} else {
// Example: "Tue, Oct 9"
fullFormat = res.getString(com.android.internal.R.string.wday_date);
}
} else {
if (showTime) {
// Example: "10:00 - 11:00 am, Oct 9"
fullFormat = res.getString(com.android.internal.R.string.time_date);
} else {
// Example: "Oct 9"
return formatter.format("%s", dateString);
}
}
} else if (showWeekDay) {
if (showTime) {
// Example: "10:00 - 11:00 am, Tue"
fullFormat = res.getString(com.android.internal.R.string.time_wday);
} else {
// Example: "Tue"
return formatter.format("%s", startWeekDayString);
}
} else if (showTime) {
return formatter.format("%s", timeString);
}
// The values that are used in a fullFormat string are specified
// by position.
return formatter.format(fullFormat, timeString, startWeekDayString, dateString);
}
/**
* Formats a date or a time according to the local conventions. There are
* lots of options that allow the caller to control, for example, if the
* time is shown, if the day of the week is shown, if the month name is
* abbreviated, if noon is shown instead of 12pm, and so on. For the
* complete list of options, see the documentation for
* {@link #formatDateRange}.
*
* Example output strings (date formats in these examples are shown using
* the US date format convention but that may change depending on the
* local settings):
*
* - 10:15am
* - 3:00pm
* - 3pm
* - 3PM
* - 08:00
* - 17:00
* - noon
* - Noon
* - midnight
* - Midnight
* - Oct 31
* - Oct 31, 2007
* - October 31, 2007
* - 10am, Oct 31
* - 17:00, Oct 31
* - Wed
* - Wednesday
* - 10am, Wed, Oct 31
* - Wed, Oct 31
* - Wednesday, Oct 31
* - Wed, Oct 31, 2007
* - Wed, October 31
* - 10/31/2007
*
*
* @param context the context is required only if the time is shown
* @param millis a point in time in UTC milliseconds
* @param flags a bit mask of formatting options
* @return a string containing the formatted date/time.
*/
public static String formatDateTime(Context context, long millis, int flags) {
return formatDateRange(context, millis, millis, flags);
}
/**
* @return a relative time string to display the time expressed by millis. Times
* are counted starting at midnight, which means that assuming that the current
* time is March 31st, 0:30:
*
* - "millis=0:10 today" will be displayed as "0:10"
* - "millis=11:30pm the day before" will be displayed as "Mar 30"
*
* If the given millis is in a different year, then the full date is
* returned in numeric format (e.g., "10/12/2008").
*
* @param withPreposition If true, the string returned will include the correct
* preposition ("at 9:20am", "on 10/12/2008" or "on May 29").
*/
public static CharSequence getRelativeTimeSpanString(Context c, long millis,
boolean withPreposition) {
String result;
long now = System.currentTimeMillis();
long span = Math.abs(now - millis);
synchronized (DateUtils.class) {
if (sNowTime == null) {
sNowTime = new Time();
}
if (sThenTime == null) {
sThenTime = new Time();
}
sNowTime.set(now);
sThenTime.set(millis);
int prepositionId;
if (span < DAY_IN_MILLIS && sNowTime.weekDay == sThenTime.weekDay) {
// Same day
int flags = FORMAT_SHOW_TIME;
result = formatDateRange(c, millis, millis, flags);
prepositionId = R.string.preposition_for_time;
} else if (sNowTime.year != sThenTime.year) {
// Different years
int flags = FORMAT_SHOW_DATE | FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE;
result = formatDateRange(c, millis, millis, flags);
// This is a date (like "10/31/2008" so use the date preposition)
prepositionId = R.string.preposition_for_date;
} else {
// Default
int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_MONTH;
result = formatDateRange(c, millis, millis, flags);
prepositionId = R.string.preposition_for_date;
}
if (withPreposition) {
Resources res = c.getResources();
result = res.getString(prepositionId, result);
}
}
return result;
}
/**
* Convenience function to return relative time string without preposition.
* @param c context for resources
* @param millis time in milliseconds
* @return {@link CharSequence} containing relative time.
* @see #getRelativeTimeSpanString(Context, long, boolean)
*/
public static CharSequence getRelativeTimeSpanString(Context c, long millis) {
return getRelativeTimeSpanString(c, millis, false /* no preposition */);
}
private static Time sNowTime;
private static Time sThenTime;
7、附上android系统 Calender类的源代码
public abstract class Calendar implements Serializable, Cloneable, Comparable {
private static final long serialVersionUID = -1807547505821590642L;
/**
* True iff the values in {@code fields[]} correspond to {@code time}. Despite the name, this
* is effectively "are the values in fields[] up-to-date?" --- {@code fields[]} may contain
* non-zero values and {@code isSet[]} may contain {@code true} values even when
* {@code areFieldsSet} is false.
* Accessing the fields via {@code get} will ensure the fields are up-to-date.
*/
protected boolean areFieldsSet;
/**
* Contains broken-down field values for the current value of {@code time} if
* {@code areFieldsSet} is true, or stale data corresponding to some previous value otherwise.
* Accessing the fields via {@code get} will ensure the fields are up-to-date.
* The array length is always {@code FIELD_COUNT}.
*/
protected int[] fields;
/**
* Whether the corresponding element in {@code field[]} has been set. Initially, these are all
* false. The first time the fields are computed, these are set to true and remain set even if
* the data becomes stale: you must check {@code areFieldsSet} if you want to know
* whether the value is up-to-date.
* Note that {@code isSet} is not a safe alternative to accessing this array directly,
* and will likewise return stale data!
* The array length is always {@code FIELD_COUNT}.
*/
protected boolean[] isSet;
/**
* Whether {@code time} corresponds to the values in {@code fields[]}. If false, {@code time}
* is out-of-date with respect to changes {@code fields[]}.
* Accessing the time via {@code getTimeInMillis} will always return the correct value.
*/
protected boolean isTimeSet;
/**
* A time in milliseconds since January 1, 1970. See {@code isTimeSet}.
* Accessing the time via {@code getTimeInMillis} will always return the correct value.
*/
protected long time;
transient int lastTimeFieldSet;
transient int lastDateFieldSet;
private boolean lenient;
private int firstDayOfWeek;
private int minimalDaysInFirstWeek;
private TimeZone zone;
/**
* Value of the {@code MONTH} field indicating the first month of the
* year.
*/
public static final int JANUARY = 0;
/**
* Value of the {@code MONTH} field indicating the second month of
* the year.
*/
public static final int FEBRUARY = 1;
/**
* Value of the {@code MONTH} field indicating the third month of the
* year.
*/
public static final int MARCH = 2;
/**
* Value of the {@code MONTH} field indicating the fourth month of
* the year.
*/
public static final int APRIL = 3;
/**
* Value of the {@code MONTH} field indicating the fifth month of the
* year.
*/
public static final int MAY = 4;
/**
* Value of the {@code MONTH} field indicating the sixth month of the
* year.
*/
public static final int JUNE = 5;
/**
* Value of the {@code MONTH} field indicating the seventh month of
* the year.
*/
public static final int JULY = 6;
/**
* Value of the {@code MONTH} field indicating the eighth month of
* the year.
*/
public static final int AUGUST = 7;
/**
* Value of the {@code MONTH} field indicating the ninth month of the
* year.
*/
public static final int SEPTEMBER = 8;
/**
* Value of the {@code MONTH} field indicating the tenth month of the
* year.
*/
public static final int OCTOBER = 9;
/**
* Value of the {@code MONTH} field indicating the eleventh month of
* the year.
*/
public static final int NOVEMBER = 10;
/**
* Value of the {@code MONTH} field indicating the twelfth month of
* the year.
*/
public static final int DECEMBER = 11;
/**
* Value of the {@code MONTH} field indicating the thirteenth month
* of the year. Although {@code GregorianCalendar} does not use this
* value, lunar calendars do.
*/
public static final int UNDECIMBER = 12;
/**
* Value of the {@code DAY_OF_WEEK} field indicating Sunday.
*/
public static final int SUNDAY = 1;
/**
* Value of the {@code DAY_OF_WEEK} field indicating Monday.
*/
public static final int MONDAY = 2;
/**
* Value of the {@code DAY_OF_WEEK} field indicating Tuesday.
*/
public static final int TUESDAY = 3;
/**
* Value of the {@code DAY_OF_WEEK} field indicating Wednesday.
*/
public static final int WEDNESDAY = 4;
/**
* Value of the {@code DAY_OF_WEEK} field indicating Thursday.
*/
public static final int THURSDAY = 5;
/**
* Value of the {@code DAY_OF_WEEK} field indicating Friday.
*/
public static final int FRIDAY = 6;
/**
* Value of the {@code DAY_OF_WEEK} field indicating Saturday.
*/
public static final int SATURDAY = 7;
/**
* Field number for {@code get} and {@code set} indicating the
* era, e.g., AD or BC in the Julian calendar. This is a calendar-specific
* value; see subclass documentation.
*
* @see GregorianCalendar#AD
* @see GregorianCalendar#BC
*/
public static final int ERA = 0;
/**
* Field number for {@code get} and {@code set} indicating the
* year. This is a calendar-specific value; see subclass documentation.
*/
public static final int YEAR = 1;
/**
* Field number for {@code get} and {@code set} indicating the
* month. This is a calendar-specific value. The first month of the year is
* {@code JANUARY}; the last depends on the number of months in a
* year.
*
* @see #JANUARY
* @see #FEBRUARY
* @see #MARCH
* @see #APRIL
* @see #MAY
* @see #JUNE
* @see #JULY
* @see #AUGUST
* @see #SEPTEMBER
* @see #OCTOBER
* @see #NOVEMBER
* @see #DECEMBER
* @see #UNDECIMBER
*/
public static final int MONTH = 2;
/**
* Field number for {@code get} and {@code set} indicating the
* week number within the current year. The first week of the year, as
* defined by {@code getFirstDayOfWeek()} and
* {@code getMinimalDaysInFirstWeek()}, has value 1. Subclasses
* define the value of {@code WEEK_OF_YEAR} for days before the first
* week of the year.
*
* @see #getFirstDayOfWeek
* @see #getMinimalDaysInFirstWeek
*/
public static final int WEEK_OF_YEAR = 3;
/**
* Field number for {@code get} and {@code set} indicating the
* week number within the current month. The first week of the month, as
* defined by {@code getFirstDayOfWeek()} and
* {@code getMinimalDaysInFirstWeek()}, has value 1. Subclasses
* define the value of {@code WEEK_OF_MONTH} for days before the
* first week of the month.
*
* @see #getFirstDayOfWeek
* @see #getMinimalDaysInFirstWeek
*/
public static final int WEEK_OF_MONTH = 4;
/**
* Field number for {@code get} and {@code set} indicating the
* day of the month. This is a synonym for {@code DAY_OF_MONTH}. The
* first day of the month has value 1.
*
* @see #DAY_OF_MONTH
*/
public static final int DATE = 5;
/**
* Field number for {@code get} and {@code set} indicating the
* day of the month. This is a synonym for {@code DATE}. The first
* day of the month has value 1.
*
* @see #DATE
*/
public static final int DAY_OF_MONTH = 5;
/**
* Field number for {@code get} and {@code set} indicating the
* day number within the current year. The first day of the year has value
* 1.
*/
public static final int DAY_OF_YEAR = 6;
/**
* Field number for {@code get} and {@code set} indicating the
* day of the week. This field takes values {@code SUNDAY},
* {@code MONDAY}, {@code TUESDAY}, {@code WEDNESDAY},
* {@code THURSDAY}, {@code FRIDAY}, and
* {@code SATURDAY}.
*
* @see #SUNDAY
* @see #MONDAY
* @see #TUESDAY
* @see #WEDNESDAY
* @see #THURSDAY
* @see #FRIDAY
* @see #SATURDAY
*/
public static final int DAY_OF_WEEK = 7;
/**
* Field number for {@code get} and {@code set} indicating the
* ordinal number of the day of the week within the current month. Together
* with the {@code DAY_OF_WEEK} field, this uniquely specifies a day
* within a month. Unlike {@code WEEK_OF_MONTH} and
* {@code WEEK_OF_YEAR}, this field's value does not
* depend on {@code getFirstDayOfWeek()} or
* {@code getMinimalDaysInFirstWeek()}. {@code DAY_OF_MONTH 1}
* through {@code 7} always correspond to DAY_OF_WEEK_IN_MONTH
* 1
;
* {@code 8} through {@code 15} correspond to
* {@code DAY_OF_WEEK_IN_MONTH 2}, and so on.
* {@code DAY_OF_WEEK_IN_MONTH 0} indicates the week before
* {@code DAY_OF_WEEK_IN_MONTH 1}. Negative values count back from
* the end of the month, so the last Sunday of a month is specified as
* {@code DAY_OF_WEEK = SUNDAY, DAY_OF_WEEK_IN_MONTH = -1}. Because
* negative values count backward they will usually be aligned differently
* within the month than positive values. For example, if a month has 31
* days, {@code DAY_OF_WEEK_IN_MONTH -1} will overlap
* {@code DAY_OF_WEEK_IN_MONTH 5} and the end of {@code 4}.
*
* @see #DAY_OF_WEEK
* @see #WEEK_OF_MONTH
*/
public static final int DAY_OF_WEEK_IN_MONTH = 8;
/**
* Field number for {@code get} and {@code set} indicating
* whether the {@code HOUR} is before or after noon. E.g., at
* 10:04:15.250 PM the {@code AM_PM} is {@code PM}.
*
* @see #AM
* @see #PM
* @see #HOUR
*/
public static final int AM_PM = 9;
/**
* Field number for {@code get} and {@code set} indicating the
* hour of the morning or afternoon. {@code HOUR} is used for the
* 12-hour clock. E.g., at 10:04:15.250 PM the {@code HOUR} is 10.
*
* @see #AM_PM
* @see #HOUR_OF_DAY
*/
public static final int HOUR = 10;
/**
* Field number for {@code get} and {@code set} indicating the
* hour of the day. {@code HOUR_OF_DAY} is used for the 24-hour
* clock. E.g., at 10:04:15.250 PM the {@code HOUR_OF_DAY} is 22.
*
* @see #HOUR
*/
public static final int HOUR_OF_DAY = 11;
/**
* Field number for {@code get} and {@code set} indicating the
* minute within the hour. E.g., at 10:04:15.250 PM the {@code MINUTE}
* is 4.
*/
public static final int MINUTE = 12;
/**
* Field number for {@code get} and {@code set} indicating the
* second within the minute. E.g., at 10:04:15.250 PM the
* {@code SECOND} is 15.
*/
public static final int SECOND = 13;
/**
* Field number for {@code get} and {@code set} indicating the
* millisecond within the second. E.g., at 10:04:15.250 PM the
* {@code MILLISECOND} is 250.
*/
public static final int MILLISECOND = 14;
/**
* Field number for {@code get} and {@code set} indicating the
* raw offset from GMT in milliseconds.
*/
public static final int ZONE_OFFSET = 15;
/**
* Field number for {@code get} and {@code set} indicating the
* daylight savings offset in milliseconds.
*/
public static final int DST_OFFSET = 16;
/**
* This is the total number of fields in this calendar.
*/
public static final int FIELD_COUNT = 17;
/**
* Value of the {@code AM_PM} field indicating the period of the day
* from midnight to just before noon.
*/
public static final int AM = 0;
/**
* Value of the {@code AM_PM} field indicating the period of the day
* from noon to just before midnight.
*/
public static final int PM = 1;
/**
* Requests both {@code SHORT} and {@code LONG} styles in the map returned by
* {@link #getDisplayNames}.
* @since 1.6
*/
public static final int ALL_STYLES = 0;
/**
* Requests short names (such as "Jan") from
* {@link #getDisplayName} or {@link #getDisplayNames}.
* @since 1.6
*/
public static final int SHORT = 1;
/**
* Requests long names (such as "January") from
* {@link #getDisplayName} or {@link #getDisplayNames}.
* @since 1.6
*/
public static final int LONG = 2;
private static final String[] FIELD_NAMES = { "ERA", "YEAR", "MONTH",
"WEEK_OF_YEAR", "WEEK_OF_MONTH", "DAY_OF_MONTH", "DAY_OF_YEAR",
"DAY_OF_WEEK", "DAY_OF_WEEK_IN_MONTH", "AM_PM", "HOUR",
"HOUR_OF_DAY", "MINUTE", "SECOND", "MILLISECOND",
"ZONE_OFFSET", "DST_OFFSET" };
/**
* Constructs a {@code Calendar} instance using the default {@code TimeZone} and {@code Locale}.
*/
protected Calendar() {
this(TimeZone.getDefault(), Locale.getDefault());
}
Calendar(TimeZone timezone) {
fields = new int[FIELD_COUNT];
isSet = new boolean[FIELD_COUNT];
areFieldsSet = isTimeSet = false;
setLenient(true);
setTimeZone(timezone);
}
/**
* Constructs a {@code Calendar} instance using the specified {@code TimeZone} and {@code Locale}.
*
* @param timezone
* the timezone.
* @param locale
* the locale.
*/
protected Calendar(TimeZone timezone, Locale locale) {
this(timezone);
LocaleData localeData = LocaleData.get(locale);
setFirstDayOfWeek(localeData.firstDayOfWeek.intValue());
setMinimalDaysInFirstWeek(localeData.minimalDaysInFirstWeek.intValue());
}
/**
* Adds the specified amount to a {@code Calendar} field.
*
* @param field
* the {@code Calendar} field to modify.
* @param value
* the amount to add to the field.
* @throws IllegalArgumentException
* if {@code field} is {@code DST_OFFSET} or {@code
* ZONE_OFFSET}.
*/
public abstract void add(int field, int value);
/**
* Returns whether the {@code Date} specified by this {@code Calendar} instance is after the {@code Date}
* specified by the parameter. The comparison is not dependent on the time
* zones of the {@code Calendar}.
*
* @param calendar
* the {@code Calendar} instance to compare.
* @return {@code true} when this Calendar is after calendar, {@code false} otherwise.
* @throws IllegalArgumentException
* if the time is not set and the time cannot be computed
* from the current field values.
*/
public boolean after(Object calendar) {
if (!(calendar instanceof Calendar)) {
return false;
}
return getTimeInMillis() > ((Calendar) calendar).getTimeInMillis();
}
/**
* Returns whether the {@code Date} specified by this {@code Calendar} instance is before the
* {@code Date} specified by the parameter. The comparison is not dependent on the
* time zones of the {@code Calendar}.
*
* @param calendar
* the {@code Calendar} instance to compare.
* @return {@code true} when this Calendar is before calendar, {@code false} otherwise.
* @throws IllegalArgumentException
* if the time is not set and the time cannot be computed
* from the current field values.
*/
public boolean before(Object calendar) {
if (!(calendar instanceof Calendar)) {
return false;
}
return getTimeInMillis() < ((Calendar) calendar).getTimeInMillis();
}
/**
* Clears all of the fields of this {@code Calendar}. All fields are initialized to
* zero.
*/
public final void clear() {
for (int i = 0; i < FIELD_COUNT; i++) {
fields[i] = 0;
isSet[i] = false;
}
areFieldsSet = isTimeSet = false;
}
/**
* Clears the specified field to zero and sets the isSet flag to {@code false}.
*
* @param field
* the field to clear.
*/
public final void clear(int field) {
fields[field] = 0;
isSet[field] = false;
areFieldsSet = isTimeSet = false;
}
/**
* Returns a new {@code Calendar} with the same properties.
*
* @return a shallow copy of this {@code Calendar}.
*
* @see java.lang.Cloneable
*/
@Override
public Object clone() {
try {
Calendar clone = (Calendar) super.clone();
clone.fields = fields.clone();
clone.isSet = isSet.clone();
clone.zone = (TimeZone) zone.clone();
return clone;
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
/**
* Computes the time from the fields if the time has not already been set.
* Computes the fields from the time if the fields are not already set.
*
* @throws IllegalArgumentException
* if the time is not set and the time cannot be computed
* from the current field values.
*/
protected void complete() {
if (!isTimeSet) {
computeTime();
isTimeSet = true;
}
if (!areFieldsSet) {
computeFields();
areFieldsSet = true;
}
}
/**
* Computes the {@code Calendar} fields from {@code time}.
*/
protected abstract void computeFields();
/**
* Computes {@code time} from the Calendar fields.
*
* @throws IllegalArgumentException
* if the time cannot be computed from the current field
* values.
*/
protected abstract void computeTime();
/**
* Compares the specified object to this {@code Calendar} and returns whether they are
* equal. The object must be an instance of {@code Calendar} and have the same
* properties.
*
* @param object
* the object to compare with this object.
* @return {@code true} if the specified object is equal to this {@code Calendar}, {@code false}
* otherwise.
*/
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (!(object instanceof Calendar)) {
return false;
}
Calendar cal = (Calendar) object;
return getTimeInMillis() == cal.getTimeInMillis()
&& isLenient() == cal.isLenient()
&& getFirstDayOfWeek() == cal.getFirstDayOfWeek()
&& getMinimalDaysInFirstWeek() == cal.getMinimalDaysInFirstWeek()
&& getTimeZone().equals(cal.getTimeZone());
}
/**
* Gets the value of the specified field after computing the field values by
* calling {@code complete()} first.
*
* @param field
* the field to get.
* @return the value of the specified field.
*
* @throws IllegalArgumentException
* if the fields are not set, the time is not set, and the
* time cannot be computed from the current field values.
* @throws ArrayIndexOutOfBoundsException
* if the field is not inside the range of possible fields.
* The range is starting at 0 up to {@code FIELD_COUNT}.
*/
public int get(int field) {
complete();
return fields[field];
}
/**
* Returns the maximum value of the specified field for the current date.
* For example, the maximum number of days in the current month.
*/
public int getActualMaximum(int field) {
int value, next;
if (getMaximum(field) == (next = getLeastMaximum(field))) {
return next;
}
complete();
long orgTime = time;
set(field, next);
do {
value = next;
roll(field, true);
next = get(field);
} while (next > value);
time = orgTime;
areFieldsSet = false;
return value;
}
/**
* Gets the minimum value of the specified field for the current date.
*
* @param field
* the field.
* @return the minimum value of the specified field.
*/
public int getActualMinimum(int field) {
int value, next;
if (getMinimum(field) == (next = getGreatestMinimum(field))) {
return next;
}
complete();
long orgTime = time;
set(field, next);
do {
value = next;
roll(field, false);
next = get(field);
} while (next < value);
time = orgTime;
areFieldsSet = false;
return value;
}
/**
* Returns an array of locales for which custom {@code Calendar} instances
* are available.
* Note that Android does not support user-supplied locale service providers.
*/
public static synchronized Locale[] getAvailableLocales() {
return ICU.getAvailableCalendarLocales();
}
/**
* Gets the first day of the week for this {@code Calendar}.
*
* @return the first day of the week.
*/
public int getFirstDayOfWeek() {
return firstDayOfWeek;
}
/**
* Gets the greatest minimum value of the specified field. This is the
* biggest value that {@code getActualMinimum} can return for any possible
* time.
*
* @param field
* the field.
* @return the greatest minimum value of the specified field.
*/
public abstract int getGreatestMinimum(int field);
/**
* Constructs a new instance of the {@code Calendar} subclass appropriate for the
* default {@code Locale}.
*
* @return a {@code Calendar} subclass instance set to the current date and time in
* the default {@code Timezone}.
*/
public static synchronized Calendar getInstance() {
return new GregorianCalendar();
}
/**
* Constructs a new instance of the {@code Calendar} subclass appropriate for the
* specified {@code Locale}.
*
* @param locale
* the locale to use.
* @return a {@code Calendar} subclass instance set to the current date and time.
*/
public static synchronized Calendar getInstance(Locale locale) {
return new GregorianCalendar(locale);
}
/**
* Constructs a new instance of the {@code Calendar} subclass appropriate for the
* default {@code Locale}, using the specified {@code TimeZone}.
*
* @param timezone
* the {@code TimeZone} to use.
* @return a {@code Calendar} subclass instance set to the current date and time in
* the specified timezone.
*/
public static synchronized Calendar getInstance(TimeZone timezone) {
return new GregorianCalendar(timezone);
}
/**
* Constructs a new instance of the {@code Calendar} subclass appropriate for the
* specified {@code Locale}.
*
* @param timezone
* the {@code TimeZone} to use.
* @param locale
* the {@code Locale} to use.
* @return a {@code Calendar} subclass instance set to the current date and time in
* the specified timezone.
*/
public static synchronized Calendar getInstance(TimeZone timezone, Locale locale) {
return new GregorianCalendar(timezone, locale);
}
/**
* Gets the smallest maximum value of the specified field. This is the
* smallest value that {@code getActualMaximum()} can return for any
* possible time.
*
* @param field
* the field number.
* @return the smallest maximum value of the specified field.
*/
public abstract int getLeastMaximum(int field);
/**
* Gets the greatest maximum value of the specified field. This returns the
* biggest value that {@code get} can return for the specified field.
*
* @param field
* the field.
* @return the greatest maximum value of the specified field.
*/
public abstract int getMaximum(int field);
/**
* Gets the minimal days in the first week of the year.
*
* @return the minimal days in the first week of the year.
*/
public int getMinimalDaysInFirstWeek() {
return minimalDaysInFirstWeek;
}
/**
* Gets the smallest minimum value of the specified field. this returns the
* smallest value thet {@code get} can return for the specified field.
*
* @param field
* the field number.
* @return the smallest minimum value of the specified field.
*/
public abstract int getMinimum(int field);
/**
* Gets the time of this {@code Calendar} as a {@code Date} object.
*
* @return a new {@code Date} initialized to the time of this {@code Calendar}.
*
* @throws IllegalArgumentException
* if the time is not set and the time cannot be computed
* from the current field values.
*/
public final Date getTime() {
return new Date(getTimeInMillis());
}
/**
* Computes the time from the fields if required and returns the time.
*
* @return the time of this {@code Calendar}.
*
* @throws IllegalArgumentException
* if the time is not set and the time cannot be computed
* from the current field values.
*/
public long getTimeInMillis() {
if (!isTimeSet) {
computeTime();
isTimeSet = true;
}
return time;
}
/**
* Gets the timezone of this {@code Calendar}.
*
* @return the {@code TimeZone} used by this {@code Calendar}.
*/
public TimeZone getTimeZone() {
return zone;
}
/**
* Returns an integer hash code for the receiver. Objects which are equal
* return the same value for this method.
*
* @return the receiver's hash.
*
* @see #equals
*/
@Override
public int hashCode() {
return (isLenient() ? 1237 : 1231) + getFirstDayOfWeek()
+ getMinimalDaysInFirstWeek() + getTimeZone().hashCode();
}
/**
* Gets the value of the specified field without recomputing.
*
* @param field
* the field.
* @return the value of the specified field.
*/
protected final int internalGet(int field) {
return fields[field];
}
/**
* Returns if this {@code Calendar} accepts field values which are outside the valid
* range for the field.
*
* @return {@code true} if this {@code Calendar} is lenient, {@code false} otherwise.
*/
public boolean isLenient() {
return lenient;
}
/**
* Returns whether the specified field is set. Note that the interpretation of "is set" is
* somewhat technical. In particular, it does not mean that the field's value is up
* to date. If you want to know whether a field contains an up-to-date value, you must also
* check {@code areFieldsSet}, making this method somewhat useless unless you're a subclass,
* in which case you can access the {@code isSet} array directly.
*
* A field remains "set" from the first time its value is computed until it's cleared by one
* of the {@code clear} methods. Thus "set" does not mean "valid". You probably want to call
* {@code get} -- which will update fields as necessary -- rather than try to make use of
* this method.
*
* @param field
* a {@code Calendar} field number.
* @return {@code true} if the specified field is set, {@code false} otherwise.
*/
public final boolean isSet(int field) {
return isSet[field];
}
/**
* Adds the specified amount to the specified field and wraps the value of
* the field when it goes beyond the maximum or minimum value for the
* current date. Other fields will be adjusted as required to maintain a
* consistent date.
*
* @param field
* the field to roll.
* @param value
* the amount to add.
*/
public void roll(int field, int value) {
boolean increment = value >= 0;
int count = increment ? value : -value;
for (int i = 0; i < count; i++) {
roll(field, increment);
}
}
/**
* Increment or decrement the specified field and wrap the value of the
* field when it goes beyond the maximum or minimum value for the current
* date. Other fields will be adjusted as required to maintain a consistent
* date.
*
* @param field
* the number indicating the field to roll.
* @param increment
* {@code true} to increment the field, {@code false} to decrement.
*/
public abstract void roll(int field, boolean increment);
/**
* Sets a field to the specified value.
*
* @param field
* the code indicating the {@code Calendar} field to modify.
* @param value
* the value.
*/
public void set(int field, int value) {
fields[field] = value;
isSet[field] = true;
areFieldsSet = isTimeSet = false;
if (field > MONTH && field < AM_PM) {
lastDateFieldSet = field;
}
if (field == HOUR || field == HOUR_OF_DAY) {
lastTimeFieldSet = field;
}
if (field == AM_PM) {
lastTimeFieldSet = HOUR;
}
}
/**
* Sets the year, month and day of the month fields. Other fields are not
* changed.
*
* @param year
* the year.
* @param month
* the month.
* @param day
* the day of the month.
*/
public final void set(int year, int month, int day) {
set(YEAR, year);
set(MONTH, month);
set(DATE, day);
}
/**
* Sets the year, month, day of the month, hour of day and minute fields.
* Other fields are not changed.
*
* @param year
* the year.
* @param month
* the month.
* @param day
* the day of the month.
* @param hourOfDay
* the hour of day.
* @param minute
* the minute.
*/
public final void set(int year, int month, int day, int hourOfDay,
int minute) {
set(year, month, day);
set(HOUR_OF_DAY, hourOfDay);
set(MINUTE, minute);
}
/**
* Sets the year, month, day of the month, hour of day, minute and second
* fields. Other fields are not changed.
*
* @param year
* the year.
* @param month
* the month.
* @param day
* the day of the month.
* @param hourOfDay
* the hour of day.
* @param minute
* the minute.
* @param second
* the second.
*/
public final void set(int year, int month, int day, int hourOfDay,
int minute, int second) {
set(year, month, day, hourOfDay, minute);
set(SECOND, second);
}
/**
* Sets the first day of the week for this {@code Calendar}.
*
* @param value
* a {@code Calendar} day of the week.
*/
public void setFirstDayOfWeek(int value) {
firstDayOfWeek = value;
}
/**
* Sets this {@code Calendar} to accept field values which are outside the valid
* range for the field.
*
* @param value
* a boolean value.
*/
public void setLenient(boolean value) {
lenient = value;
}
/**
* Sets the minimal days in the first week of the year.
*
* @param value
* the minimal days in the first week of the year.
*/
public void setMinimalDaysInFirstWeek(int value) {
minimalDaysInFirstWeek = value;
}
/**
* Sets the time of this {@code Calendar}.
*
* @param date
* a {@code Date} object.
*/
public final void setTime(Date date) {
setTimeInMillis(date.getTime());
}
/**
* Sets the time of this {@code Calendar}.
*
* @param milliseconds
* the time as the number of milliseconds since Jan. 1, 1970.
*/
public void setTimeInMillis(long milliseconds) {
if (!isTimeSet || !areFieldsSet || time != milliseconds) {
time = milliseconds;
isTimeSet = true;
areFieldsSet = false;
complete();
}
}
/**
* Sets the {@code TimeZone} used by this Calendar.
*
* @param timezone
* a {@code TimeZone}.
*/
public void setTimeZone(TimeZone timezone) {
zone = timezone;
areFieldsSet = false;
}
/**
* Returns the string representation of this {@code Calendar}.
*/
@Override
public String toString() {
StringBuilder result = new StringBuilder(getClass().getName() +
"[time=" + (isTimeSet ? String.valueOf(time) : "?") +
",areFieldsSet=" + areFieldsSet +
",lenient=" + lenient +
",zone=" + zone.getID() +
",firstDayOfWeek=" + firstDayOfWeek +
",minimalDaysInFirstWeek=" + minimalDaysInFirstWeek);
for (int i = 0; i < FIELD_COUNT; i++) {
result.append(',');
result.append(FIELD_NAMES[i]);
result.append('=');
if (isSet[i]) {
result.append(fields[i]);
} else {
result.append('?');
}
}
result.append(']');
return result.toString();
}
/**
* Compares the times of the two {@code Calendar}, which represent the milliseconds
* from the January 1, 1970 00:00:00.000 GMT (Gregorian).
*
* @param anotherCalendar
* another calendar that this one is compared with.
* @return 0 if the times of the two {@code Calendar}s are equal, -1 if the time of
* this {@code Calendar} is before the other one, 1 if the time of this
* {@code Calendar} is after the other one.
* @throws NullPointerException
* if the argument is null.
* @throws IllegalArgumentException
* if the argument does not include a valid time
* value.
*/
public int compareTo(Calendar anotherCalendar) {
if (anotherCalendar == null) {
throw new NullPointerException("anotherCalendar == null");
}
long timeInMillis = getTimeInMillis();
long anotherTimeInMillis = anotherCalendar.getTimeInMillis();
if (timeInMillis > anotherTimeInMillis) {
return 1;
}
if (timeInMillis == anotherTimeInMillis) {
return 0;
}
return -1;
}
/**
* Returns a human-readable string for the value of {@code field}
* using the given style and locale. If no string is available, returns null.
* The value is retrieved by invoking {@code get(field)}.
*
*
For example, {@code getDisplayName(MONTH, SHORT, Locale.US)} will return "Jan"
* while {@code getDisplayName(MONTH, LONG, Locale.US)} will return "January".
*
* @param field the field
* @param style {@code SHORT} or {@code LONG}
* @param locale the locale
* @return the display name, or null
* @throws NullPointerException if {@code locale == null}
* @throws IllegalArgumentException if {@code field} or {@code style} is invalid
* @since 1.6
*/
public String getDisplayName(int field, int style, Locale locale) {
// TODO: the RI's documentation says ALL_STYLES is invalid, but actually treats it as SHORT.
if (style == ALL_STYLES) {
style = SHORT;
}
String[] array = getDisplayNameArray(field, style, locale);
int value = get(field);
return (array != null) ? array[value] : null;
}
private String[] getDisplayNameArray(int field, int style, Locale locale) {
if (field < 0 || field >= FIELD_COUNT) {
throw new IllegalArgumentException("bad field " + field);
}
checkStyle(style);
DateFormatSymbols dfs = DateFormatSymbols.getInstance(locale);
switch (field) {
case AM_PM:
return dfs.getAmPmStrings();
case DAY_OF_WEEK:
return (style == LONG) ? dfs.getWeekdays() : dfs.getShortWeekdays();
case ERA:
return dfs.getEras();
case MONTH:
return (style == LONG) ? dfs.getMonths() : dfs.getShortMonths();
}
return null;
}
private static void checkStyle(int style) {
if (style != ALL_STYLES && style != SHORT && style != LONG) {
throw new IllegalArgumentException("bad style " + style);
}
}
/**
* Returns a map of human-readable strings to corresponding values,
* for the given field, style, and locale.
* Returns null if no strings are available.
*
*
For example, {@code getDisplayNames(MONTH, ALL_STYLES, Locale.US)} would
* contain mappings from "Jan" and "January" to {@link #JANUARY}, and so on.
*
* @param field the field
* @param style {@code SHORT}, {@code LONG}, or {@code ALL_STYLES}
* @param locale the locale
* @return the display name, or null
* @throws NullPointerException if {@code locale == null}
* @throws IllegalArgumentException if {@code field} or {@code style} is invalid
* @since 1.6
*/
public Map getDisplayNames(int field, int style, Locale locale) {
checkStyle(style);
complete();
Map result = new HashMap();
if (style == SHORT || style == ALL_STYLES) {
insertValuesInMap(result, getDisplayNameArray(field, SHORT, locale));
}
if (style == LONG || style == ALL_STYLES) {
insertValuesInMap(result, getDisplayNameArray(field, LONG, locale));
}
return result.isEmpty() ? null : result;
}
private static void insertValuesInMap(Map map, String[] values) {
if (values == null) {
return;
}
for (int i = 0; i < values.length; ++i) {
if (values[i] != null && !values[i].isEmpty()) {
map.put(values[i], i);
}
}
}
private static final ObjectStreamField[] serialPersistentFields = {
new ObjectStreamField("areFieldsSet", boolean.class),
new ObjectStreamField("fields", int[].class),
new ObjectStreamField("firstDayOfWeek", int.class),
new ObjectStreamField("isSet", boolean[].class),
new ObjectStreamField("isTimeSet", boolean.class),
new ObjectStreamField("lenient", boolean.class),
new ObjectStreamField("minimalDaysInFirstWeek", int.class),
new ObjectStreamField("nextStamp", int.class),
new ObjectStreamField("serialVersionOnStream", int.class),
new ObjectStreamField("time", long.class),
new ObjectStreamField("zone", TimeZone.class),
};
private void writeObject(ObjectOutputStream stream) throws IOException {
complete();
ObjectOutputStream.PutField putFields = stream.putFields();
putFields.put("areFieldsSet", areFieldsSet);
putFields.put("fields", this.fields);
putFields.put("firstDayOfWeek", firstDayOfWeek);
putFields.put("isSet", isSet);
putFields.put("isTimeSet", isTimeSet);
putFields.put("lenient", lenient);
putFields.put("minimalDaysInFirstWeek", minimalDaysInFirstWeek);
putFields.put("nextStamp", 2 /* MINIMUM_USER_STAMP */);
putFields.put("serialVersionOnStream", 1);
putFields.put("time", time);
putFields.put("zone", zone);
stream.writeFields();
}
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField readFields = stream.readFields();
areFieldsSet = readFields.get("areFieldsSet", false);
this.fields = (int[]) readFields.get("fields", null);
firstDayOfWeek = readFields.get("firstDayOfWeek", Calendar.SUNDAY);
isSet = (boolean[]) readFields.get("isSet", null);
isTimeSet = readFields.get("isTimeSet", false);
lenient = readFields.get("lenient", true);
minimalDaysInFirstWeek = readFields.get("minimalDaysInFirstWeek", 1);
time = readFields.get("time", 0L);
zone = (TimeZone) readFields.get("zone", null);
}