Skip to content

Instantly share code, notes, and snippets.

@nimaiwalsh
Last active July 6, 2022 04:22
Show Gist options
  • Save nimaiwalsh/e3fe152a6ed07e40c00e8b4e40e9b388 to your computer and use it in GitHub Desktop.
Save nimaiwalsh/e3fe152a6ed07e40c00e8b4e40e9b388 to your computer and use it in GitHub Desktop.
DateFormatter - Custom date and time formatter implementing custom formatting rules
import android.content.Context
import java.time.temporal.ChronoField.DAY_OF_YEAR
import java.time.temporal.ChronoField.INSTANT_SECONDS
import java.time.temporal.ChronoField.YEAR
import java.time.temporal.TemporalAccessor
import java.util.Locale
import javax.inject.Inject
/**
* Custom date and time formatter implementing custom formatting rules.
* For more information on the rules
* [see here][https://www.figma.com/file/UvEcD4fWXvkuJnBbaw4EhRxs/Design-System-Mobile-Library?node-id=1414%3A4062].
*/
class DateFormatter @Inject constructor(
val context: Context,
/** The selected [Locale] to use for this DateFormatter instance */
val locale: Locale,
/** The date & time format to be used */
private val formatProvider: DateTimePatternProvider,
) {
// Long date format
private val longDateFormat = formatProvider.getDateTimeFormatterForSkeletonPattern(locale, "EEE MMM d yyyy")
// Short date format (omits the year)
private val dateFormat = formatProvider.getDateTimeFormatterForSkeletonPattern(locale, "EEE MMM d")
// Time format driven purely by locale
private val timeFormat = formatProvider.getDateTimeFormatterForSkeletonPattern(locale, "jmma")
// Time format used when the 24-hour setting is enabled (i.e. overrides the locale format)
private val timeFormat24Hour = formatProvider.getDateTimeFormatterForSkeletonPattern(locale, "HH:mm")
companion object {
const val DATE_TIME_SEPARATOR = ", "
const val RANGE_SEPARATOR = " - "
}
/** @return the formatted time component of the given temporal value, e.g "10:00am", "1:00pm", "19:15" */
fun formatTime(temporal: TemporalAccessor): String {
return if (formatProvider.is24HourFormat(context)) {
timeFormat24Hour.format(temporal)
} else {
timeFormat.format(temporal)
}
}
/**
* @return the formatted time range given the start and end temporal values,
* e.g "8:00am - 9:45pm", 11:15am - 1:00pm
*/
fun formatTimeRange(startTemporal: TemporalAccessor, endTemporal: TemporalAccessor): String {
require(startTemporal.getInstantSeconds() <= endTemporal.getInstantSeconds()) {
"startTemporal must be before endTemporal"
}
return "${formatTime(startTemporal)}$RANGE_SEPARATOR${formatTime(endTemporal)}"
}
/** @return the formatted date component of the given temporal value, e.g "Fri Jan 1 */
fun formatDate(temporal: TemporalAccessor): String {
return dateFormat.format(temporal)
}
/** */
fun formatLongDateAndTime(temporal: TemporalAccessor): String {
return "${longDateFormat.format(temporal)}$DATE_TIME_SEPARATOR${formatTime(temporal)}"
}
/** @return the formatted date and time components of the given temporal value, e.g. "Fri Jan 1, 3:30pm" */
fun formatDateAndTime(temporal: TemporalAccessor): String {
return "${formatDate(temporal)}$DATE_TIME_SEPARATOR${formatTime(temporal)}"
}
/**
* @return the formatted date and time range of the given start and end temporal values,
* e.g "Wed Mar 3, 8am - 9am", "Thu Apr 14, 9:30pm - Fri Apr 15, 6am"
*/
fun formatDateAndTimeRange(startTemporal: TemporalAccessor, endTemporal: TemporalAccessor): String {
require(startTemporal.getInstantSeconds() <= endTemporal.getInstantSeconds()) {
"startTemporal must be before endTemporal"
}
val isMultiDay = startTemporal[DAY_OF_YEAR] != endTemporal[DAY_OF_YEAR] ||
startTemporal[YEAR] != endTemporal[YEAR]
val startString = formatDateAndTime(startTemporal)
val endString = if (isMultiDay) {
formatDateAndTime(endTemporal)
} else {
formatTime(endTemporal)
}
return "$startString$RANGE_SEPARATOR$endString"
}
}
/** @return the [INSTANT_SECONDS] value of this [TemporalAccessor] */
private fun TemporalAccessor.getInstantSeconds(): Long = getLong(INSTANT_SECONDS)
import android.content.Context
import android.text.format.DateFormat
import com.skedulo.core.di.ApplicationScope
import com.squareup.anvil.annotations.ContributesBinding
import java.time.format.DateTimeFormatter
import java.util.Locale
import javax.inject.Inject
interface DateTimePatternProvider {
fun getDateTimeFormatterForSkeletonPattern(locale: Locale, skeleton: String): DateTimeFormatter
fun is24HourFormat(context: Context): Boolean
}
@ContributesBinding(ApplicationScope::class)
class DateTimePatternProviderImpl @Inject constructor() : DateTimePatternProvider {
override fun getDateTimeFormatterForSkeletonPattern(locale: Locale, skeleton: String): DateTimeFormatter {
return DateTimeFormatter.ofPattern(
DateFormat.getBestDateTimePattern(locale, skeleton)
)
}
override fun is24HourFormat(context: Context): Boolean {
return DateFormat.is24HourFormat(context)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment