1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.providers.media.photopicker.util; 18 19 import static android.icu.text.DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE; 20 import static android.icu.text.RelativeDateTimeFormatter.Style.LONG; 21 22 import android.icu.text.DateFormat; 23 import android.icu.text.DisplayContext; 24 import android.icu.text.RelativeDateTimeFormatter; 25 import android.icu.text.RelativeDateTimeFormatter.AbsoluteUnit; 26 import android.icu.text.RelativeDateTimeFormatter.Direction; 27 import android.icu.util.ULocale; 28 import android.text.format.DateUtils; 29 30 import androidx.annotation.VisibleForTesting; 31 32 import java.time.Instant; 33 import java.time.LocalDate; 34 import java.time.LocalDateTime; 35 import java.time.ZoneId; 36 import java.time.format.TextStyle; 37 import java.time.temporal.ChronoUnit; 38 import java.util.Locale; 39 40 /** 41 * Provide the utility methods to handle date time. 42 */ 43 public class DateTimeUtils { 44 45 private static final String DATE_FORMAT_SKELETON_WITH_YEAR = "EMMMdy"; 46 private static final String DATE_FORMAT_SKELETON_WITHOUT_YEAR = "EMMMd"; 47 private static final String DATE_FORMAT_SKELETON_WITH_TIME = "MMMdyhmmss"; 48 49 /** 50 * Formats a time according to the local conventions for PhotoGrid. 51 * 52 * If the difference of the date between the time and now is zero, show 53 * "Today". 54 * If the difference is 1, show "Yesterday". 55 * If the difference is less than 7, show the weekday. E.g. "Sunday". 56 * Otherwise, show the weekday and the date. E.g. "Sat, Jun 5". 57 * If they have different years, show the weekday, the date and the year. 58 * E.g. "Sat, Jun 5, 2021" 59 * 60 * @param when the time to be formatted. The unit is in milliseconds 61 * since January 1, 1970 00:00:00.0 UTC. 62 * @return the formatted string 63 */ getDateHeaderString(long when)64 public static String getDateHeaderString(long when) { 65 // Get the system time zone 66 final ZoneId zoneId = ZoneId.systemDefault(); 67 final LocalDate nowDate = LocalDate.now(zoneId); 68 69 return getDateHeaderString(when, nowDate); 70 } 71 72 /** 73 * Formats a time according to the local conventions for content description. 74 * 75 * The format of the returned string is fixed to {@code DATE_FORMAT_SKELETON_WITH_TIME}. 76 * E.g. "Feb 2, 2022, 2:22:22 PM" 77 * 78 * @param when the time to be formatted. The unit is in milliseconds 79 * since January 1, 1970 00:00:00.0 UTC. 80 * @return the formatted string 81 */ getDateTimeStringForContentDesc(long when)82 public static String getDateTimeStringForContentDesc(long when) { 83 return getDateTimeString(when, DATE_FORMAT_SKELETON_WITH_TIME, Locale.getDefault()); 84 } 85 86 @VisibleForTesting getDateHeaderString(long when, LocalDate nowDate)87 static String getDateHeaderString(long when, LocalDate nowDate) { 88 // Get the system time zone 89 final ZoneId zoneId = ZoneId.systemDefault(); 90 final LocalDate whenDate = LocalDateTime.ofInstant(Instant.ofEpochMilli(when), 91 zoneId).toLocalDate(); 92 93 final long dayDiff = ChronoUnit.DAYS.between(whenDate, nowDate); 94 if (dayDiff == 0) { 95 return getTodayString(); 96 } else if (dayDiff == 1) { 97 return getYesterdayString(); 98 } else if (dayDiff > 0 && dayDiff < 7) { 99 return whenDate.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.getDefault()); 100 } else { 101 final String skeleton; 102 if (whenDate.getYear() == nowDate.getYear()) { 103 skeleton = DATE_FORMAT_SKELETON_WITHOUT_YEAR; 104 } else { 105 skeleton = DATE_FORMAT_SKELETON_WITH_YEAR; 106 } 107 108 return getDateTimeString(when, skeleton, Locale.getDefault()); 109 } 110 } 111 112 @VisibleForTesting getDateTimeString(long when, String skeleton, Locale locale)113 static String getDateTimeString(long when, String skeleton, Locale locale) { 114 final DateFormat format = DateFormat.getInstanceForSkeleton(skeleton, locale); 115 format.setContext(DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE); 116 return format.format(when); 117 } 118 119 /** 120 * It is borrowed from {@link DateUtils} since it is no official API yet. 121 * 122 * @param oneMillis the first time. The unit is in milliseconds since 123 * January 1, 1970 00:00:00.0 UTC. 124 * @param twoMillis the second time. The unit is in milliseconds since 125 * January 1, 1970 00:00:00.0 UTC. 126 * @return True, the date is the same. Otherwise, return false. 127 */ isSameDate(long oneMillis, long twoMillis)128 public static boolean isSameDate(long oneMillis, long twoMillis) { 129 // Get the system time zone 130 final ZoneId zoneId = ZoneId.systemDefault(); 131 132 final Instant oneInstant = Instant.ofEpochMilli(oneMillis); 133 final LocalDateTime oneLocalDateTime = LocalDateTime.ofInstant(oneInstant, zoneId); 134 135 final Instant twoInstant = Instant.ofEpochMilli(twoMillis); 136 final LocalDateTime twoLocalDateTime = LocalDateTime.ofInstant(twoInstant, zoneId); 137 138 return (oneLocalDateTime.getYear() == twoLocalDateTime.getYear()) 139 && (oneLocalDateTime.getMonthValue() == twoLocalDateTime.getMonthValue()) 140 && (oneLocalDateTime.getDayOfMonth() == twoLocalDateTime.getDayOfMonth()); 141 } 142 143 @VisibleForTesting getTodayString()144 static String getTodayString() { 145 final RelativeDateTimeFormatter fmt = RelativeDateTimeFormatter.getInstance( 146 ULocale.getDefault(), null, LONG, CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE); 147 return fmt.format(Direction.THIS, AbsoluteUnit.DAY); 148 } 149 150 @VisibleForTesting getYesterdayString()151 static String getYesterdayString() { 152 final RelativeDateTimeFormatter fmt = RelativeDateTimeFormatter.getInstance( 153 ULocale.getDefault(), null, LONG, CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE); 154 return fmt.format(Direction.LAST, AbsoluteUnit.DAY); 155 } 156 } 157