1fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian/* 2fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian * Copyright (C) 2017 The Android Open Source Project 3fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian * 4fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian * Licensed under the Apache License, Version 2.0 (the "License"); 5fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian * you may not use this file except in compliance with the License. 6fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian * You may obtain a copy of the License at 7fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian * 8fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian * http://www.apache.org/licenses/LICENSE-2.0 9fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian * 10fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian * Unless required by applicable law or agreed to in writing, software 11fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian * distributed under the License is distributed on an "AS IS" BASIS, 12fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian * See the License for the specific language governing permissions and 14fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian * limitations under the License. 15fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian */ 16fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian 17fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanianpackage com.android.dialer.calllogutils; 18fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian 19fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanianimport android.content.Context; 20fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanianimport android.icu.lang.UCharacter; 21fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanianimport android.icu.text.BreakIterator; 22fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanianimport android.os.Build.VERSION; 23fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanianimport android.os.Build.VERSION_CODES; 24fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanianimport android.text.format.DateUtils; 25fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanianimport java.util.Calendar; 26fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanianimport java.util.Locale; 27fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanianimport java.util.concurrent.TimeUnit; 28fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian 29fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian/** Static methods for formatting dates in the call log. */ 30fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanianpublic final class CallLogDates { 31fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian 32fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian /** 33fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian * Uses the new date formatting rules to format dates in the new call log. 34fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian * 35fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian * <p>Rules: 36fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian * 37fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian * <pre> 385680b01ecb566e60a63c3a3362ec31f912cef692linyuh * if < 1 minute ago: "Just now"; 39861d8cd2dc75f80e5a6480145bbe29a1f5156acflinyuh * else if < 1 hour ago: time relative to now (e.g., "8 min ago"); 405680b01ecb566e60a63c3a3362ec31f912cef692linyuh * else if today: time (e.g., "12:15 PM"); 415680b01ecb566e60a63c3a3362ec31f912cef692linyuh * else if < 7 days: abbreviated day of week (e.g., "Wed"); 425680b01ecb566e60a63c3a3362ec31f912cef692linyuh * else if < 1 year: date with abbreviated month, day, but no year (e.g., "Jan 15"); 435680b01ecb566e60a63c3a3362ec31f912cef692linyuh * else: date with abbreviated month, day, and year (e.g., "Jan 15, 2018"). 44fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian * </pre> 45fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian */ 46fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian public static CharSequence newCallLogTimestampLabel( 47fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian Context context, long nowMillis, long timestampMillis) { 485680b01ecb566e60a63c3a3362ec31f912cef692linyuh // For calls logged less than 1 minute ago, display "Just now". 49fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian if (nowMillis - timestampMillis < TimeUnit.MINUTES.toMillis(1)) { 505680b01ecb566e60a63c3a3362ec31f912cef692linyuh return context.getString(R.string.just_now); 51fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian } 525680b01ecb566e60a63c3a3362ec31f912cef692linyuh 53861d8cd2dc75f80e5a6480145bbe29a1f5156acflinyuh // For calls logged less than 1 hour ago, display time relative to now (e.g., "8 min ago"). 545680b01ecb566e60a63c3a3362ec31f912cef692linyuh if (nowMillis - timestampMillis < TimeUnit.HOURS.toMillis(1)) { 555680b01ecb566e60a63c3a3362ec31f912cef692linyuh return DateUtils.getRelativeTimeSpanString( 56861d8cd2dc75f80e5a6480145bbe29a1f5156acflinyuh timestampMillis, 57861d8cd2dc75f80e5a6480145bbe29a1f5156acflinyuh nowMillis, 58861d8cd2dc75f80e5a6480145bbe29a1f5156acflinyuh DateUtils.MINUTE_IN_MILLIS, 59861d8cd2dc75f80e5a6480145bbe29a1f5156acflinyuh DateUtils.FORMAT_ABBREV_RELATIVE) 60861d8cd2dc75f80e5a6480145bbe29a1f5156acflinyuh .toString() 61861d8cd2dc75f80e5a6480145bbe29a1f5156acflinyuh // The platform method DateUtils#getRelativeTimeSpanString adds a dot ('.') after the 62861d8cd2dc75f80e5a6480145bbe29a1f5156acflinyuh // abbreviated time unit for some languages (e.g., "8 min. ago") but we prefer not to have 63861d8cd2dc75f80e5a6480145bbe29a1f5156acflinyuh // the dot. 64861d8cd2dc75f80e5a6480145bbe29a1f5156acflinyuh .replace(".", ""); 655680b01ecb566e60a63c3a3362ec31f912cef692linyuh } 665680b01ecb566e60a63c3a3362ec31f912cef692linyuh 675680b01ecb566e60a63c3a3362ec31f912cef692linyuh int dayDifference = getDayDifference(nowMillis, timestampMillis); 685680b01ecb566e60a63c3a3362ec31f912cef692linyuh 695680b01ecb566e60a63c3a3362ec31f912cef692linyuh // For calls logged today, display time (e.g., "12:15 PM"). 705680b01ecb566e60a63c3a3362ec31f912cef692linyuh if (dayDifference == 0) { 715680b01ecb566e60a63c3a3362ec31f912cef692linyuh return DateUtils.formatDateTime(context, timestampMillis, DateUtils.FORMAT_SHOW_TIME); 72fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian } 735680b01ecb566e60a63c3a3362ec31f912cef692linyuh 745680b01ecb566e60a63c3a3362ec31f912cef692linyuh // For calls logged within a week, display the abbreviated day of week (e.g., "Wed"). 755680b01ecb566e60a63c3a3362ec31f912cef692linyuh if (dayDifference < 7) { 765680b01ecb566e60a63c3a3362ec31f912cef692linyuh return formatDayOfWeek(context, timestampMillis); 775680b01ecb566e60a63c3a3362ec31f912cef692linyuh } 785680b01ecb566e60a63c3a3362ec31f912cef692linyuh 795680b01ecb566e60a63c3a3362ec31f912cef692linyuh // For calls logged within a year, display abbreviated month, day, but no year (e.g., "Jan 15"). 805680b01ecb566e60a63c3a3362ec31f912cef692linyuh if (isWithinOneYear(nowMillis, timestampMillis)) { 815680b01ecb566e60a63c3a3362ec31f912cef692linyuh return formatAbbreviatedDate(context, timestampMillis, /* showYear = */ false); 82fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian } 835680b01ecb566e60a63c3a3362ec31f912cef692linyuh 845680b01ecb566e60a63c3a3362ec31f912cef692linyuh // For calls logged no less than one year ago, display abbreviated month, day, and year 855680b01ecb566e60a63c3a3362ec31f912cef692linyuh // (e.g., "Jan 15, 2018"). 865680b01ecb566e60a63c3a3362ec31f912cef692linyuh return formatAbbreviatedDate(context, timestampMillis, /* showYear = */ true); 87fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian } 88fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian 89fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian /** 905680b01ecb566e60a63c3a3362ec31f912cef692linyuh * Formats the provided timestamp (in milliseconds) into date and time suitable for display in the 915680b01ecb566e60a63c3a3362ec31f912cef692linyuh * current locale. 92fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian * 93fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian * <p>For example, returns a string like "Wednesday, May 25, 2016, 8:02PM" or "Chorshanba, 2016 94fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian * may 25,20:02". 95fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian * 96fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian * <p>For pre-N devices, the returned value may not start with a capital if the local convention 97fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian * is to not capitalize day names. On N+ devices, the returned value is always capitalized. 98fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian */ 995680b01ecb566e60a63c3a3362ec31f912cef692linyuh public static CharSequence formatDate(Context context, long timestamp) { 100fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian return toTitleCase( 101fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian DateUtils.formatDateTime( 102fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian context, 1035680b01ecb566e60a63c3a3362ec31f912cef692linyuh timestamp, 104fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian DateUtils.FORMAT_SHOW_TIME 105fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian | DateUtils.FORMAT_SHOW_DATE 106fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian | DateUtils.FORMAT_SHOW_WEEKDAY 107fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian | DateUtils.FORMAT_SHOW_YEAR)); 108fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian } 109fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian 110fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian /** 1115680b01ecb566e60a63c3a3362ec31f912cef692linyuh * Formats the provided timestamp (in milliseconds) into abbreviated day of week. 112fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian * 1135680b01ecb566e60a63c3a3362ec31f912cef692linyuh * <p>For example, returns a string like "Wed" or "Chor". 114fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian * 115fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian * <p>For pre-N devices, the returned value may not start with a capital if the local convention 116fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian * is to not capitalize day names. On N+ devices, the returned value is always capitalized. 117fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian */ 1185680b01ecb566e60a63c3a3362ec31f912cef692linyuh private static CharSequence formatDayOfWeek(Context context, long timestamp) { 119fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian return toTitleCase( 1205680b01ecb566e60a63c3a3362ec31f912cef692linyuh DateUtils.formatDateTime( 1215680b01ecb566e60a63c3a3362ec31f912cef692linyuh context, timestamp, DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_WEEKDAY)); 122fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian } 123fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian 124fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian /** 1255680b01ecb566e60a63c3a3362ec31f912cef692linyuh * Formats the provided timestamp (in milliseconds) into the month abbreviation, day, and 1265680b01ecb566e60a63c3a3362ec31f912cef692linyuh * optionally, year. 127fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian * 1285680b01ecb566e60a63c3a3362ec31f912cef692linyuh * <p>For example, returns a string like "Jan 15" or "Jan 15, 2018". 129fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian * 130fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian * <p>For pre-N devices, the returned value may not start with a capital if the local convention 131fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian * is to not capitalize day names. On N+ devices, the returned value is always capitalized. 132fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian */ 1335680b01ecb566e60a63c3a3362ec31f912cef692linyuh private static CharSequence formatAbbreviatedDate( 1345680b01ecb566e60a63c3a3362ec31f912cef692linyuh Context context, long timestamp, boolean showYear) { 1355680b01ecb566e60a63c3a3362ec31f912cef692linyuh int flags = DateUtils.FORMAT_ABBREV_MONTH; 1365680b01ecb566e60a63c3a3362ec31f912cef692linyuh if (!showYear) { 1375680b01ecb566e60a63c3a3362ec31f912cef692linyuh flags |= DateUtils.FORMAT_NO_YEAR; 1385680b01ecb566e60a63c3a3362ec31f912cef692linyuh } 1395680b01ecb566e60a63c3a3362ec31f912cef692linyuh 1405680b01ecb566e60a63c3a3362ec31f912cef692linyuh return toTitleCase(DateUtils.formatDateTime(context, timestamp, flags)); 141fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian } 142fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian 143fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian private static CharSequence toTitleCase(CharSequence value) { 144fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian // We want the beginning of the date string to be capitalized, even if the word at the beginning 145fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian // of the string is not usually capitalized. For example, "Wednesdsay" in Uzbek is "chorshanba” 146fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian // (not capitalized). To handle this issue we apply title casing to the start of the sentence so 147fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian // that "chorshanba, 2016 may 25,20:02" becomes "Chorshanba, 2016 may 25,20:02". 148fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian // 149fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian // The ICU library was not available in Android until N, so we can only do this in N+ devices. 150fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian // Pre-N devices will still see incorrect capitalization in some languages. 151fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian if (VERSION.SDK_INT < VERSION_CODES.N) { 152fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian return value; 153fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian } 154fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian 155fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian // Using the ICU library is safer than just applying toUpperCase() on the first letter of the 156fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian // word because in some languages, there can be multiple starting characters which should be 157fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian // upper-cased together. For example in Dutch "ij" is a digraph in which both letters should be 158fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian // capitalized together. 159fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian 160fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian // TITLECASE_NO_LOWERCASE is necessary so that things that are already capitalized are not 161fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian // lower-cased as part of the conversion. 162fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian return UCharacter.toTitleCase( 163fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian Locale.getDefault(), 164fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian value.toString(), 165fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian BreakIterator.getSentenceInstance(), 166fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian UCharacter.TITLECASE_NO_LOWERCASE); 167fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian } 168fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian 169fdaa46618ce61344bc83a66590863d126c47b05flinyuh /** 170fdaa46618ce61344bc83a66590863d126c47b05flinyuh * Returns the absolute difference in days between two timestamps. It is the caller's 171fdaa46618ce61344bc83a66590863d126c47b05flinyuh * responsibility to ensure both timestamps are in milliseconds. Failure to do so will result in 172fdaa46618ce61344bc83a66590863d126c47b05flinyuh * undefined behavior. 173fdaa46618ce61344bc83a66590863d126c47b05flinyuh * 174fdaa46618ce61344bc83a66590863d126c47b05flinyuh * <p>Note that the difference is based on day boundaries, not 24-hour periods. 175fdaa46618ce61344bc83a66590863d126c47b05flinyuh * 176fdaa46618ce61344bc83a66590863d126c47b05flinyuh * <p>Examples: 177fdaa46618ce61344bc83a66590863d126c47b05flinyuh * 178fdaa46618ce61344bc83a66590863d126c47b05flinyuh * <ul> 179fdaa46618ce61344bc83a66590863d126c47b05flinyuh * <li>The difference between 01/19/2018 00:00 and 01/19/2018 23:59 is 0. 180fdaa46618ce61344bc83a66590863d126c47b05flinyuh * <li>The difference between 01/18/2018 23:59 and 01/19/2018 23:59 is 1. 181fdaa46618ce61344bc83a66590863d126c47b05flinyuh * <li>The difference between 01/18/2018 00:00 and 01/19/2018 23:59 is 1. 182fdaa46618ce61344bc83a66590863d126c47b05flinyuh * <li>The difference between 01/17/2018 23:59 and 01/19/2018 00:00 is 2. 183fdaa46618ce61344bc83a66590863d126c47b05flinyuh * </ul> 184fdaa46618ce61344bc83a66590863d126c47b05flinyuh */ 185fdaa46618ce61344bc83a66590863d126c47b05flinyuh public static int getDayDifference(long firstTimestamp, long secondTimestamp) { 1865680b01ecb566e60a63c3a3362ec31f912cef692linyuh // Ensure secondTimestamp is no less than firstTimestamp 187fdaa46618ce61344bc83a66590863d126c47b05flinyuh if (secondTimestamp < firstTimestamp) { 188fdaa46618ce61344bc83a66590863d126c47b05flinyuh long t = firstTimestamp; 189fdaa46618ce61344bc83a66590863d126c47b05flinyuh firstTimestamp = secondTimestamp; 190fdaa46618ce61344bc83a66590863d126c47b05flinyuh secondTimestamp = t; 191fdaa46618ce61344bc83a66590863d126c47b05flinyuh } 192fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian 193fdaa46618ce61344bc83a66590863d126c47b05flinyuh // Use secondTimestamp as reference 194fdaa46618ce61344bc83a66590863d126c47b05flinyuh Calendar startOfReferenceDay = Calendar.getInstance(); 195fdaa46618ce61344bc83a66590863d126c47b05flinyuh startOfReferenceDay.setTimeInMillis(secondTimestamp); 196fdaa46618ce61344bc83a66590863d126c47b05flinyuh 197fdaa46618ce61344bc83a66590863d126c47b05flinyuh // This is attempting to find the start of the reference day, but it's not quite right due to 198fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian // daylight savings. Unfortunately there doesn't seem to be a way to get the correct start of 199fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian // the day without using Joda or Java8, both of which are disallowed. This means that the wrong 200fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian // formatting may be applied on days with time changes (though the displayed values will be 201fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian // correct). 202fdaa46618ce61344bc83a66590863d126c47b05flinyuh startOfReferenceDay.add(Calendar.HOUR_OF_DAY, -startOfReferenceDay.get(Calendar.HOUR_OF_DAY)); 203fdaa46618ce61344bc83a66590863d126c47b05flinyuh startOfReferenceDay.add(Calendar.MINUTE, -startOfReferenceDay.get(Calendar.MINUTE)); 204fdaa46618ce61344bc83a66590863d126c47b05flinyuh startOfReferenceDay.add(Calendar.SECOND, -startOfReferenceDay.get(Calendar.SECOND)); 205fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian 206fdaa46618ce61344bc83a66590863d126c47b05flinyuh Calendar other = Calendar.getInstance(); 207fdaa46618ce61344bc83a66590863d126c47b05flinyuh other.setTimeInMillis(firstTimestamp); 208fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian 209fdaa46618ce61344bc83a66590863d126c47b05flinyuh int dayDifference = 0; 210fdaa46618ce61344bc83a66590863d126c47b05flinyuh while (other.before(startOfReferenceDay)) { 211fdaa46618ce61344bc83a66590863d126c47b05flinyuh startOfReferenceDay.add(Calendar.DATE, -1); 212fdaa46618ce61344bc83a66590863d126c47b05flinyuh dayDifference++; 213fdaa46618ce61344bc83a66590863d126c47b05flinyuh } 214fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian 215fdaa46618ce61344bc83a66590863d126c47b05flinyuh return dayDifference; 216fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian } 217fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian 2185680b01ecb566e60a63c3a3362ec31f912cef692linyuh /** 2195680b01ecb566e60a63c3a3362ec31f912cef692linyuh * Returns true if the two timestamps are within one year. It is the caller's responsibility to 2205680b01ecb566e60a63c3a3362ec31f912cef692linyuh * ensure both timestamps are in milliseconds. Failure to do so will result in undefined behavior. 2215680b01ecb566e60a63c3a3362ec31f912cef692linyuh * 2225680b01ecb566e60a63c3a3362ec31f912cef692linyuh * <p>Note that the difference is based on 365/366-day periods. 2235680b01ecb566e60a63c3a3362ec31f912cef692linyuh * 2245680b01ecb566e60a63c3a3362ec31f912cef692linyuh * <p>Examples: 2255680b01ecb566e60a63c3a3362ec31f912cef692linyuh * 2265680b01ecb566e60a63c3a3362ec31f912cef692linyuh * <ul> 2275680b01ecb566e60a63c3a3362ec31f912cef692linyuh * <li>01/01/2018 00:00 and 12/31/2018 23:59 is within one year. 2285680b01ecb566e60a63c3a3362ec31f912cef692linyuh * <li>12/31/2017 23:59 and 12/31/2018 23:59 is not within one year. 2295680b01ecb566e60a63c3a3362ec31f912cef692linyuh * <li>12/31/2017 23:59 and 01/01/2018 00:00 is within one year. 2305680b01ecb566e60a63c3a3362ec31f912cef692linyuh * </ul> 2315680b01ecb566e60a63c3a3362ec31f912cef692linyuh */ 2325680b01ecb566e60a63c3a3362ec31f912cef692linyuh private static boolean isWithinOneYear(long firstTimestamp, long secondTimestamp) { 2335680b01ecb566e60a63c3a3362ec31f912cef692linyuh // Ensure secondTimestamp is no less than firstTimestamp 2345680b01ecb566e60a63c3a3362ec31f912cef692linyuh if (secondTimestamp < firstTimestamp) { 2355680b01ecb566e60a63c3a3362ec31f912cef692linyuh long t = firstTimestamp; 2365680b01ecb566e60a63c3a3362ec31f912cef692linyuh firstTimestamp = secondTimestamp; 2375680b01ecb566e60a63c3a3362ec31f912cef692linyuh secondTimestamp = t; 2385680b01ecb566e60a63c3a3362ec31f912cef692linyuh } 2395680b01ecb566e60a63c3a3362ec31f912cef692linyuh 2405680b01ecb566e60a63c3a3362ec31f912cef692linyuh // Use secondTimestamp as reference 2415680b01ecb566e60a63c3a3362ec31f912cef692linyuh Calendar reference = Calendar.getInstance(); 2425680b01ecb566e60a63c3a3362ec31f912cef692linyuh reference.setTimeInMillis(secondTimestamp); 2435680b01ecb566e60a63c3a3362ec31f912cef692linyuh reference.add(Calendar.YEAR, -1); 244fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian 2455680b01ecb566e60a63c3a3362ec31f912cef692linyuh Calendar other = Calendar.getInstance(); 2465680b01ecb566e60a63c3a3362ec31f912cef692linyuh other.setTimeInMillis(firstTimestamp); 247fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian 2485680b01ecb566e60a63c3a3362ec31f912cef692linyuh return reference.before(other); 249fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian } 250fc0eb8ccebcc7846db5e8b5c5430070055679bfaEric Erfanian} 251