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.temporal.ChronoUnit; 37 import java.util.Locale; 38 39 /** 40 * Provide the utility methods to handle date time. 41 */ 42 public class DateTimeUtils { 43 44 private static final String DATE_FORMAT_SKELETON_WITH_YEAR = "EMMMdy"; 45 private static final String DATE_FORMAT_SKELETON_WITHOUT_YEAR = "EMMMd"; 46 private static final String DATE_FORMAT_SKELETON_WITH_TIME = "MMMdyhmmss"; 47 48 /** 49 * Formats a time according to the local conventions for PhotoGrid. 50 * 51 * If the difference of the date between the time and now is zero, show 52 * "Today". 53 * If the difference is 1, show "Yesterday". 54 * If the difference is less than 7, show the weekday. E.g. "Sunday". 55 * Otherwise, show the weekday and the date. E.g. "Sat, Jun 5". 56 * If they have different years, show the weekday, the date and the year. 57 * E.g. "Sat, Jun 5, 2021" 58 * 59 * @param when the time to be formatted. The unit is in milliseconds 60 * since January 1, 1970 00:00:00.0 UTC. 61 * @return the formatted string 62 */ getDateHeaderString(long when)63 public static String getDateHeaderString(long when) { 64 // Get the system time zone 65 final ZoneId zoneId = ZoneId.systemDefault(); 66 final LocalDate nowDate = LocalDate.now(zoneId); 67 68 return getDateHeaderString(when, nowDate); 69 } 70 71 /** 72 * Formats a time according to the local conventions for content description. 73 * 74 * The format of the returned string is fixed to {@code DATE_FORMAT_SKELETON_WITH_TIME}. 75 * E.g. "Feb 2, 2022, 2:22:22 PM" 76 * 77 * @param when the time to be formatted. The unit is in milliseconds 78 * since January 1, 1970 00:00:00.0 UTC. 79 * @return the formatted string 80 */ getDateTimeStringForContentDesc(long when)81 public static String getDateTimeStringForContentDesc(long when) { 82 return getDateTimeString(when, DATE_FORMAT_SKELETON_WITH_TIME, Locale.getDefault()); 83 } 84 85 @VisibleForTesting getDateHeaderString(long when, LocalDate nowDate)86 static String getDateHeaderString(long when, LocalDate nowDate) { 87 // Get the system time zone 88 final ZoneId zoneId = ZoneId.systemDefault(); 89 final LocalDate whenDate = LocalDateTime.ofInstant(Instant.ofEpochMilli(when), 90 zoneId).toLocalDate(); 91 92 final long dayDiff = ChronoUnit.DAYS.between(whenDate, nowDate); 93 if (dayDiff == 0) { 94 return getTodayString(); 95 } else if (dayDiff == 1) { 96 return getYesterdayString(); 97 } else { 98 final String skeleton; 99 if (dayDiff > 0 && dayDiff < 7) { 100 skeleton = DateFormat.WEEKDAY; 101 } else if (whenDate.getYear() == nowDate.getYear()) { 102 skeleton = DATE_FORMAT_SKELETON_WITHOUT_YEAR; 103 } else { 104 skeleton = DATE_FORMAT_SKELETON_WITH_YEAR; 105 } 106 107 return getDateTimeString(when, skeleton, Locale.getDefault()); 108 } 109 } 110 111 @VisibleForTesting getDateTimeString(long when, String skeleton, Locale locale)112 static String getDateTimeString(long when, String skeleton, Locale locale) { 113 final DateFormat format = DateFormat.getInstanceForSkeleton(skeleton, locale); 114 format.setContext(DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE); 115 return format.format(when); 116 } 117 118 /** 119 * It is borrowed from {@link DateUtils} since it is no official API yet. 120 * 121 * @param oneMillis the first time. The unit is in milliseconds since 122 * January 1, 1970 00:00:00.0 UTC. 123 * @param twoMillis the second time. The unit is in milliseconds since 124 * January 1, 1970 00:00:00.0 UTC. 125 * @return True, the date is the same. Otherwise, return false. 126 */ isSameDate(long oneMillis, long twoMillis)127 public static boolean isSameDate(long oneMillis, long twoMillis) { 128 // Get the system time zone 129 final ZoneId zoneId = ZoneId.systemDefault(); 130 131 final Instant oneInstant = Instant.ofEpochMilli(oneMillis); 132 final LocalDateTime oneLocalDateTime = LocalDateTime.ofInstant(oneInstant, zoneId); 133 134 final Instant twoInstant = Instant.ofEpochMilli(twoMillis); 135 final LocalDateTime twoLocalDateTime = LocalDateTime.ofInstant(twoInstant, zoneId); 136 137 return (oneLocalDateTime.getYear() == twoLocalDateTime.getYear()) 138 && (oneLocalDateTime.getMonthValue() == twoLocalDateTime.getMonthValue()) 139 && (oneLocalDateTime.getDayOfMonth() == twoLocalDateTime.getDayOfMonth()); 140 } 141 142 @VisibleForTesting getTodayString()143 static String getTodayString() { 144 final RelativeDateTimeFormatter fmt = RelativeDateTimeFormatter.getInstance( 145 ULocale.getDefault(), null, LONG, CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE); 146 return fmt.format(Direction.THIS, AbsoluteUnit.DAY); 147 } 148 149 @VisibleForTesting getYesterdayString()150 static String getYesterdayString() { 151 final RelativeDateTimeFormatter fmt = RelativeDateTimeFormatter.getInstance( 152 ULocale.getDefault(), null, LONG, CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE); 153 return fmt.format(Direction.LAST, AbsoluteUnit.DAY); 154 } 155 } 156