@@ -4,7 +4,11 @@ package to.bitkit.ext
44
55import android.icu.text.DateFormat
66import android.icu.text.DisplayContext
7+ import android.icu.text.NumberFormat
78import android.icu.text.RelativeDateTimeFormatter
9+ import android.icu.text.RelativeDateTimeFormatter.AbsoluteUnit
10+ import android.icu.text.RelativeDateTimeFormatter.Direction
11+ import android.icu.text.RelativeDateTimeFormatter.RelativeUnit
812import android.icu.util.ULocale
913import kotlinx.datetime.Clock
1014import kotlinx.datetime.LocalDate
@@ -62,67 +66,33 @@ fun Long.toRelativeTimeString(
6266 val now = nowMillis(clock)
6367 val diffMillis = now - this
6468
69+ val uLocale = ULocale .forLocale(locale)
70+ val numberFormat = NumberFormat .getNumberInstance(uLocale)?.apply { maximumFractionDigits = 0 }
71+
6572 val formatter = RelativeDateTimeFormatter .getInstance(
66- ULocale .forLocale(locale) ,
67- null ,
73+ uLocale ,
74+ numberFormat ,
6875 RelativeDateTimeFormatter .Style .LONG ,
69- DisplayContext .CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE ,
76+ DisplayContext .CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE ,
7077 ) ? : return toLocalizedTimestamp()
7178
72- val seconds = diffMillis / Constants .MILLIS_TO_SECONDS
73- val minutes = ( seconds / Constants .SECONDS_TO_MINUTES ).toInt()
74- val hours = ( minutes / Constants .MINUTES_TO_HOURS ).toInt()
75- val days = ( hours / Constants .HOURS_TO_DAYS ).toInt()
76- val weeks = ( days / Constants .DAYS_TO_WEEKS ).toInt()
77- val months = ( days / Constants .DAYS_TO_MONTHS ).toInt()
78- val years = ( days / Constants .DAYS_TO_YEARS ).toInt()
79+ val seconds = diffMillis / Factor .MILLIS_TO_SECONDS
80+ val minutes = seconds / Factor .SECONDS_TO_MINUTES
81+ val hours = minutes / Factor .MINUTES_TO_HOURS
82+ val days = hours / Factor .HOURS_TO_DAYS
83+ val weeks = days / Factor .DAYS_TO_WEEKS
84+ val months = days / Factor .DAYS_TO_MONTHS
85+ val years = days / Factor .DAYS_TO_YEARS
7986
8087 return when {
81- seconds < Constants .SECONDS_THRESHOLD -> formatter.format(
82- RelativeDateTimeFormatter .Direction .PLAIN ,
83- RelativeDateTimeFormatter .AbsoluteUnit .NOW ,
84- )
85-
86- minutes < Constants .MINUTES_THRESHOLD -> formatter.format(
87- minutes.toDouble(),
88- RelativeDateTimeFormatter .Direction .LAST ,
89- RelativeDateTimeFormatter .RelativeUnit .MINUTES ,
90- )
91-
92- hours < Constants .HOURS_THRESHOLD -> formatter.format(
93- hours.toDouble(),
94- RelativeDateTimeFormatter .Direction .LAST ,
95- RelativeDateTimeFormatter .RelativeUnit .HOURS ,
96- )
97-
98- days < Constants .YESTERDAY_THRESHOLD -> formatter.format(
99- RelativeDateTimeFormatter .Direction .LAST ,
100- RelativeDateTimeFormatter .AbsoluteUnit .DAY ,
101- )
102-
103- days < Constants .DAYS_THRESHOLD -> formatter.format(
104- days.toDouble(),
105- RelativeDateTimeFormatter .Direction .LAST ,
106- RelativeDateTimeFormatter .RelativeUnit .DAYS ,
107- )
108-
109- weeks < Constants .WEEKS_THRESHOLD -> formatter.format(
110- weeks.toDouble(),
111- RelativeDateTimeFormatter .Direction .LAST ,
112- RelativeDateTimeFormatter .RelativeUnit .WEEKS ,
113- )
114-
115- months < Constants .MONTHS_THRESHOLD -> formatter.format(
116- months.toDouble(),
117- RelativeDateTimeFormatter .Direction .LAST ,
118- RelativeDateTimeFormatter .RelativeUnit .MONTHS ,
119- )
120-
121- else -> formatter.format(
122- years.toDouble(),
123- RelativeDateTimeFormatter .Direction .LAST ,
124- RelativeDateTimeFormatter .RelativeUnit .YEARS ,
125- )
88+ seconds < Threshold .SECONDS -> formatter.format(Direction .PLAIN , AbsoluteUnit .NOW )
89+ minutes < Threshold .MINUTES -> formatter.format(minutes, Direction .LAST , RelativeUnit .MINUTES )
90+ hours < Threshold .HOURS -> formatter.format(hours, Direction .LAST , RelativeUnit .HOURS )
91+ days < Threshold .YESTERDAY -> formatter.format(Direction .LAST , AbsoluteUnit .DAY )
92+ days < Threshold .DAYS -> formatter.format(days, Direction .LAST , RelativeUnit .DAYS )
93+ weeks < Threshold .WEEKS -> formatter.format(weeks, Direction .LAST , RelativeUnit .WEEKS )
94+ months < Threshold .MONTHS -> formatter.format(months, Direction .LAST , RelativeUnit .MONTHS )
95+ else -> formatter.format(years, Direction .LAST , RelativeUnit .YEARS )
12696 }
12797}
12898
@@ -158,41 +128,43 @@ fun getDaysInMonth(month: LocalDate): List<LocalDate?> {
158128}
159129
160130fun isLeapYear (year : Int ): Boolean {
161- return (year % Constants .LEAP_YEAR_DIVISOR_4 == 0 && year % Constants .LEAP_YEAR_DIVISOR_100 != 0 ) || (year % Constants .LEAP_YEAR_DIVISOR_400 == 0 )
131+ return (year % Constants .LEAP_YEAR_DIVISOR_4 == 0 && year % Constants .LEAP_YEAR_DIVISOR_100 != 0 ) ||
132+ (year % Constants .LEAP_YEAR_DIVISOR_400 == 0 )
162133}
163134
164- fun isDateInRange (dateMillis : Long , startMillis : Long? , endMillis : Long? ): Boolean {
135+ fun isDateInRange (
136+ dateMillis : Long ,
137+ startMillis : Long? ,
138+ endMillis : Long? ,
139+ zone : TimeZone = TimeZone .currentSystemDefault(),
140+ ): Boolean {
165141 if (startMillis == null ) return false
166142 val end = endMillis ? : startMillis
167143
168- val normalizedDate =
169- kotlinx.datetime.Instant .fromEpochMilliseconds(dateMillis).toLocalDateTime(TimeZone .currentSystemDefault()).date
170- val normalizedStart = kotlinx.datetime.Instant .fromEpochMilliseconds(startMillis)
171- .toLocalDateTime(TimeZone .currentSystemDefault()).date
172- val normalizedEnd =
173- kotlinx.datetime.Instant .fromEpochMilliseconds(end).toLocalDateTime(TimeZone .currentSystemDefault()).date
144+ val normalizedDate = kotlinx.datetime.Instant .fromEpochMilliseconds(dateMillis).toLocalDateTime(zone).date
145+ val normalizedStart = kotlinx.datetime.Instant .fromEpochMilliseconds(startMillis).toLocalDateTime(zone).date
146+ val normalizedEnd = kotlinx.datetime.Instant .fromEpochMilliseconds(end).toLocalDateTime(zone).date
174147
175- return normalizedDate >= normalizedStart && normalizedDate <= normalizedEnd
148+ return normalizedDate in normalizedStart.. normalizedEnd
176149}
177150
178- fun LocalDate.toMonthYearString (): String {
179- val formatter = SimpleDateFormat (DatePattern .MONTH_YEAR_FORMAT , Locale .getDefault() )
151+ fun LocalDate.toMonthYearString (locale : Locale = Locale .getDefault() ): String {
152+ val formatter = SimpleDateFormat (DatePattern .MONTH_YEAR_FORMAT , locale )
180153 val calendar = Calendar .getInstance()
181154 calendar.set(year, monthNumber - CalendarConstants .MONTH_INDEX_OFFSET , Constants .FIRST_DAY_OF_MONTH )
182155 return formatter.format(calendar.time)
183156}
184157
185158fun LocalDate.minusMonths (months : Int ): LocalDate =
186- this . toJavaLocalDate().minusMonths(months.toLong()).withDayOfMonth(1 ) // Always use first day of month for display
159+ toJavaLocalDate().minusMonths(months.toLong()).withDayOfMonth(1 ) // Always use first day of month for display
187160 .toKotlinLocalDate()
188161
189162fun LocalDate.plusMonths (months : Int ): LocalDate =
190- this . toJavaLocalDate().plusMonths(months.toLong()).withDayOfMonth(1 ) // Always use first day of month for display
163+ toJavaLocalDate().plusMonths(months.toLong()).withDayOfMonth(1 ) // Always use first day of month for display
191164 .toKotlinLocalDate()
192165
193- fun LocalDate.endOfDay (): Long {
194- return this .atStartOfDayIn(TimeZone .currentSystemDefault()).plus(1 .days).minus(1 .milliseconds).toEpochMilliseconds()
195- }
166+ fun LocalDate.endOfDay (zone : TimeZone = TimeZone .currentSystemDefault()): Long =
167+ atStartOfDayIn(zone).plus(1 .days).minus(1 .milliseconds).toEpochMilliseconds()
196168
197169fun utcDateFormatterOf (pattern : String ) = SimpleDateFormat (pattern, Locale .US ).apply {
198170 timeZone = java.util.TimeZone .getTimeZone(" UTC" )
@@ -215,31 +187,33 @@ object DatePattern {
215187}
216188
217189private object Constants {
218- // Time conversion factors
190+ // Calendar
191+ const val FIRST_DAY_OF_MONTH = 1
192+
193+ // Leap year calculation
194+ const val LEAP_YEAR_DIVISOR_4 = 4
195+ const val LEAP_YEAR_DIVISOR_100 = 100
196+ const val LEAP_YEAR_DIVISOR_400 = 400
197+ }
198+
199+ private object Factor {
219200 const val MILLIS_TO_SECONDS = 1000.0
220201 const val SECONDS_TO_MINUTES = 60.0
221202 const val MINUTES_TO_HOURS = 60.0
222203 const val HOURS_TO_DAYS = 24.0
223204 const val DAYS_TO_WEEKS = 7.0
224205 const val DAYS_TO_MONTHS = 30.0
225206 const val DAYS_TO_YEARS = 365.0
207+ }
226208
227- // Time unit thresholds
228- const val SECONDS_THRESHOLD = 60
229- const val MINUTES_THRESHOLD = 60
230- const val HOURS_THRESHOLD = 24
231- const val YESTERDAY_THRESHOLD = 2
232- const val DAYS_THRESHOLD = 7
233- const val WEEKS_THRESHOLD = 4
234- const val MONTHS_THRESHOLD = 12
235-
236- // Calendar
237- const val FIRST_DAY_OF_MONTH = 1
238-
239- // Leap year calculation
240- const val LEAP_YEAR_DIVISOR_4 = 4
241- const val LEAP_YEAR_DIVISOR_100 = 100
242- const val LEAP_YEAR_DIVISOR_400 = 400
209+ private object Threshold {
210+ const val SECONDS = 60
211+ const val MINUTES = 60
212+ const val HOURS = 24
213+ const val YESTERDAY = 2
214+ const val DAYS = 7
215+ const val WEEKS = 4
216+ const val MONTHS = 12
243217}
244218
245219object CalendarConstants {
0 commit comments