1 /**
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * ```
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  * ```
10  *
11  * Unless required by applicable law or agreed to in writing, software distributed under the License
12  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13  * or implied. See the License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 package com.android.healthconnect.controller.utils
17 
18 import android.content.Context
19 import android.text.format.DateFormat.*
20 import android.text.format.DateUtils
21 import com.android.healthconnect.controller.R
22 import dagger.hilt.android.qualifiers.ApplicationContext
23 import java.time.Instant
24 import java.time.ZoneId
25 import java.time.format.DateTimeFormatter
26 import java.util.Locale
27 import javax.inject.Inject
28 
29 /** Formatter for printing time and time ranges. */
30 class LocalDateTimeFormatter @Inject constructor(@ApplicationContext private val context: Context) {
31 
32     companion object {
33         // Example: "Sun, Aug 20, 2023"
34         private const val WEEKDAY_DATE_FORMAT_FLAGS_WITH_YEAR: Int =
35             DateUtils.FORMAT_SHOW_WEEKDAY or
36                 DateUtils.FORMAT_SHOW_DATE or
37                 DateUtils.FORMAT_ABBREV_ALL
38 
39         // Example: "Sun, Aug 20"
40         private const val WEEKDAY_DATE_FORMAT_FLAGS_WITHOUT_YEAR: Int =
41             WEEKDAY_DATE_FORMAT_FLAGS_WITH_YEAR or DateUtils.FORMAT_NO_YEAR
42 
43         // Example: "Aug 20, 2023"
44         private const val DATE_FORMAT_FLAGS_WITH_YEAR: Int =
45             DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_ABBREV_ALL
46 
47         // Example: "Aug 20"
48         private const val DATE_FORMAT_FLAGS_WITHOUT_YEAR: Int =
49             DATE_FORMAT_FLAGS_WITH_YEAR or DateUtils.FORMAT_NO_YEAR
50 
51         // Example: "August 2023".
52         private const val MONTH_FORMAT_FLAGS_WITH_YEAR: Int =
53             DateUtils.FORMAT_SHOW_DATE or
54                 DateUtils.FORMAT_SHOW_YEAR or
55                 DateUtils.FORMAT_NO_MONTH_DAY
56 
57         // Example: "August".
58         private const val MONTH_FORMAT_FLAGS_WITHOUT_YEAR: Int =
59             DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NO_YEAR or DateUtils.FORMAT_NO_MONTH_DAY
60     }
61 
<lambda>null62     private val timeFormat by lazy { getTimeFormat(context) }
<lambda>null63     private val longDateFormat by lazy { getLongDateFormat(context) }
<lambda>null64     private val shortDateFormat by lazy {
65         val systemFormat = getBestDateTimePattern(Locale.getDefault(), "dMMMM")
66         DateTimeFormatter.ofPattern(systemFormat, Locale.getDefault())
67     }
<lambda>null68     private val monthAndYearFormat by lazy {
69         val systemFormat = getBestDateTimePattern(Locale.getDefault(), "MMMMYYYY")
70         DateTimeFormatter.ofPattern(systemFormat, Locale.getDefault())
71     }
<lambda>null72     private val monthFormat by lazy {
73         val systemFormat = getBestDateTimePattern(Locale.getDefault(), "MMMM")
74         DateTimeFormatter.ofPattern(systemFormat, Locale.getDefault())
75     }
76 
77     /** Returns localized time. */
formatTimenull78     fun formatTime(instant: Instant): String {
79         return timeFormat.format(instant.toEpochMilli())
80     }
81 
82     /** Returns localized long versions of date. */
formatLongDatenull83     fun formatLongDate(instant: Instant): String {
84         return longDateFormat.format(instant.toEpochMilli())
85     }
86 
87     /** Returns localized short versions of date, such as "15 August" */
formatShortDatenull88     fun formatShortDate(instant: Instant): String {
89         return instant.atZone(ZoneId.systemDefault()).format(shortDateFormat)
90     }
91 
92     /** Returns localized time range. */
formatTimeRangenull93     fun formatTimeRange(start: Instant, end: Instant): String {
94         return context.getString(R.string.time_range, formatTime(start), formatTime(end))
95     }
96 
97     /** Returns accessible and localized time range. */
formatTimeRangeA11ynull98     fun formatTimeRangeA11y(start: Instant, end: Instant): String {
99         return context.getString(R.string.time_range_long, formatTime(start), formatTime(end))
100     }
101 
102     /** Formats date with weekday and year (e.g. "Sun, Aug 20, 2023"). */
formatWeekdayDateWithYearnull103     fun formatWeekdayDateWithYear(time: Instant): String {
104         return DateUtils.formatDateTime(
105             context,
106             time.toEpochMilli(),
107             WEEKDAY_DATE_FORMAT_FLAGS_WITH_YEAR or DateUtils.FORMAT_ABBREV_ALL)
108     }
109 
110     /** Formats date with weekday (e.g. "Sun, Aug 20"). */
formatWeekdayDateWithoutYearnull111     fun formatWeekdayDateWithoutYear(time: Instant): String {
112         return DateUtils.formatDateTime(
113             context,
114             time.toEpochMilli(),
115             WEEKDAY_DATE_FORMAT_FLAGS_WITHOUT_YEAR or DateUtils.FORMAT_ABBREV_ALL)
116     }
117 
118     /** Formats date range with year(e.g. "Aug 21 - 27, 2023", "Aug 28 - Sept 3, 2023"). */
formatDateRangeWithYearnull119     fun formatDateRangeWithYear(startTime: Instant, endTime: Instant): String {
120         return DateUtils.formatDateRange(
121             context, startTime.toEpochMilli(), endTime.toEpochMilli(), DATE_FORMAT_FLAGS_WITH_YEAR)
122     }
123 
124     /** Formats date range (e.g. "Aug 21 - 27", "Aug 28 - Sept 3"). */
formatDateRangeWithoutYearnull125     fun formatDateRangeWithoutYear(startTime: Instant, endTime: Instant): String {
126         return DateUtils.formatDateRange(
127             context,
128             startTime.toEpochMilli(),
129             endTime.toEpochMilli(),
130             DATE_FORMAT_FLAGS_WITHOUT_YEAR)
131     }
132 
133     /** Formats month and year (e.g. "August 2023"). */
formatMonthWithYearnull134     fun formatMonthWithYear(time: Instant): String {
135         return DateUtils.formatDateTime(context, time.toEpochMilli(), MONTH_FORMAT_FLAGS_WITH_YEAR)
136     }
137 
138     /** Formats month (e.g. "August"). */
formatMonthWithoutYearnull139     fun formatMonthWithoutYear(time: Instant): String {
140         return DateUtils.formatDateTime(
141             context, time.toEpochMilli(), MONTH_FORMAT_FLAGS_WITHOUT_YEAR)
142     }
143 }
144