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.getBestDateTimePattern 20 import android.text.format.DateFormat.getLongDateFormat 21 import android.text.format.DateFormat.getTimeFormat 22 import android.text.format.DateFormat.is24HourFormat 23 import android.text.format.DateUtils 24 import com.android.healthconnect.controller.R 25 import dagger.hilt.android.qualifiers.ApplicationContext 26 import java.time.Instant 27 import java.time.ZoneId 28 import java.time.format.DateTimeFormatter 29 import java.util.Locale 30 import javax.inject.Inject 31 32 /** Formatter for printing time and time ranges. */ 33 class LocalDateTimeFormatter @Inject constructor(@ApplicationContext private val context: Context) { 34 35 companion object { 36 // Example: "Sun, Aug 20, 2023" 37 private const val WEEKDAY_DATE_FORMAT_FLAGS_WITH_YEAR: Int = 38 DateUtils.FORMAT_SHOW_WEEKDAY or 39 DateUtils.FORMAT_SHOW_DATE or 40 DateUtils.FORMAT_ABBREV_ALL 41 42 // Example: "Sun, Aug 20" 43 private const val WEEKDAY_DATE_FORMAT_FLAGS_WITHOUT_YEAR: Int = 44 WEEKDAY_DATE_FORMAT_FLAGS_WITH_YEAR or DateUtils.FORMAT_NO_YEAR 45 46 // Example: "Aug 20, 2023" 47 private const val DATE_FORMAT_FLAGS_WITH_YEAR: Int = 48 DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_ABBREV_ALL 49 50 // Example: "Aug 20" 51 private const val DATE_FORMAT_FLAGS_WITHOUT_YEAR: Int = 52 DATE_FORMAT_FLAGS_WITH_YEAR or DateUtils.FORMAT_NO_YEAR 53 54 // Example: "August 2023". 55 private const val MONTH_FORMAT_FLAGS_WITH_YEAR: Int = 56 DateUtils.FORMAT_SHOW_DATE or 57 DateUtils.FORMAT_SHOW_YEAR or 58 DateUtils.FORMAT_NO_MONTH_DAY 59 60 // Example: "August". 61 private const val MONTH_FORMAT_FLAGS_WITHOUT_YEAR: Int = 62 DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NO_YEAR or DateUtils.FORMAT_NO_MONTH_DAY 63 } 64 65 private val timeFormat: java.text.DateFormat 66 get() = getTimeFormat(context) 67 68 private val longDateFormat: java.text.DateFormat 69 get() = getLongDateFormat(context) 70 71 private val shortDateFormat: DateTimeFormatter 72 get() { 73 val systemFormat = getBestDateTimePattern(Locale.getDefault(), "dMMMM") 74 return DateTimeFormatter.ofPattern(systemFormat, Locale.getDefault()) 75 } 76 77 // Example: "Aug 20, 14:06" 78 private val dateAndTime24HourFormat: DateTimeFormatter 79 get() { 80 val systemFormat = getBestDateTimePattern(Locale.getDefault(), "MMMd Hm") 81 return DateTimeFormatter.ofPattern(systemFormat, Locale.getDefault()) 82 } 83 84 // Example: "Aug 20, 2:06PM" 85 private val dateAndTime12HourFormat: DateTimeFormatter 86 get() { 87 val systemFormat = getBestDateTimePattern(Locale.getDefault(), "MMMd hm") 88 return DateTimeFormatter.ofPattern(systemFormat, Locale.getDefault()) 89 } 90 91 /** Returns localized time. */ formatTimenull92 fun formatTime(instant: Instant): String { 93 return timeFormat.format(instant.toEpochMilli()) 94 } 95 96 /** Returns localized long versions of date, such as "15 August 2022". */ formatLongDatenull97 fun formatLongDate(instant: Instant): String { 98 return longDateFormat.format(instant.toEpochMilli()) 99 } 100 101 /** Returns localized short versions of date, such as "15 August" */ formatShortDatenull102 fun formatShortDate(instant: Instant): String { 103 return instant.atZone(ZoneId.systemDefault()).format(shortDateFormat) 104 } 105 106 /** Returns localized short versions of date, such as "15 Aug" */ formatShortDateWithoutYearnull107 fun formatShortDateWithoutYear(startTime: Instant): String { 108 return DateUtils.formatDateTime( 109 context, 110 startTime.toEpochMilli(), 111 DATE_FORMAT_FLAGS_WITHOUT_YEAR, 112 ) 113 } 114 115 /** Returns localized short versions of date, such as "15 Aug, 2022" */ formatShortDateWithYearnull116 fun formatShortDateWithYear(startTime: Instant): String { 117 return DateUtils.formatDateTime( 118 context, 119 startTime.toEpochMilli(), 120 DATE_FORMAT_FLAGS_WITH_YEAR, 121 ) 122 } 123 124 /** Returns localized short versions of date and time, such as "15 Aug, 14:06" */ formatDateAndTimenull125 fun formatDateAndTime(instant: Instant): String { 126 val format = 127 if (is24HourFormat(context)) { 128 dateAndTime24HourFormat 129 } else { 130 dateAndTime12HourFormat 131 } 132 return instant.atZone(ZoneId.systemDefault()).format(format) 133 } 134 135 /** Returns localized time range. */ formatTimeRangenull136 fun formatTimeRange(start: Instant, end: Instant): String { 137 return context.getString(R.string.time_range, formatTime(start), formatTime(end)) 138 } 139 140 /** Returns accessible and localized time range. */ formatTimeRangeA11ynull141 fun formatTimeRangeA11y(start: Instant, end: Instant): String { 142 return context.getString(R.string.time_range_long, formatTime(start), formatTime(end)) 143 } 144 145 /** Formats date with weekday and year (e.g. "Sun, Aug 20, 2023"). */ formatWeekdayDateWithYearnull146 fun formatWeekdayDateWithYear(time: Instant): String { 147 return DateUtils.formatDateTime( 148 context, 149 time.toEpochMilli(), 150 WEEKDAY_DATE_FORMAT_FLAGS_WITH_YEAR or DateUtils.FORMAT_ABBREV_ALL, 151 ) 152 } 153 154 /** Formats date with weekday (e.g. "Sun, Aug 20"). */ formatWeekdayDateWithoutYearnull155 fun formatWeekdayDateWithoutYear(time: Instant): String { 156 return DateUtils.formatDateTime( 157 context, 158 time.toEpochMilli(), 159 WEEKDAY_DATE_FORMAT_FLAGS_WITHOUT_YEAR or DateUtils.FORMAT_ABBREV_ALL, 160 ) 161 } 162 163 /** Formats date range with year(e.g. "Aug 21 - 27, 2023", "Aug 28 - Sept 3, 2023"). */ formatDateRangeWithYearnull164 fun formatDateRangeWithYear(startTime: Instant, endTime: Instant): String { 165 return DateUtils.formatDateRange( 166 context, 167 startTime.toEpochMilli(), 168 endTime.toEpochMilli(), 169 DATE_FORMAT_FLAGS_WITH_YEAR, 170 ) 171 } 172 173 /** Formats date range (e.g. "Aug 21 - 27", "Aug 28 - Sept 3"). */ formatDateRangeWithoutYearnull174 fun formatDateRangeWithoutYear(startTime: Instant, endTime: Instant): String { 175 return DateUtils.formatDateRange( 176 context, 177 startTime.toEpochMilli(), 178 endTime.toEpochMilli(), 179 DATE_FORMAT_FLAGS_WITHOUT_YEAR, 180 ) 181 } 182 183 /** Formats month and year (e.g. "August 2023"). */ formatMonthWithYearnull184 fun formatMonthWithYear(time: Instant): String { 185 return DateUtils.formatDateTime(context, time.toEpochMilli(), MONTH_FORMAT_FLAGS_WITH_YEAR) 186 } 187 188 /** Formats month (e.g. "August"). */ formatMonthWithoutYearnull189 fun formatMonthWithoutYear(time: Instant): String { 190 return DateUtils.formatDateTime( 191 context, 192 time.toEpochMilli(), 193 MONTH_FORMAT_FLAGS_WITHOUT_YEAR, 194 ) 195 } 196 } 197