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