@@ -2,13 +2,23 @@ package to.bitkit.ext
22
33import android.icu.text.DateFormat
44import android.icu.util.ULocale
5+ import kotlinx.datetime.LocalDate
6+ import kotlinx.datetime.TimeZone
7+ import kotlinx.datetime.atStartOfDayIn
8+ import kotlinx.datetime.toJavaLocalDate
9+ import kotlinx.datetime.toKotlinLocalDate
10+ import kotlinx.datetime.toLocalDateTime
11+ import java.text.SimpleDateFormat
512import java.time.Instant
613import java.time.LocalDateTime
714import java.time.ZoneId
815import java.time.format.DateTimeFormatter
916import java.time.temporal.ChronoUnit
17+ import java.util.Calendar
1018import java.util.Date
1119import java.util.Locale
20+ import kotlin.time.Duration.Companion.days
21+ import kotlin.time.Duration.Companion.milliseconds
1222
1323fun nowTimestamp (): Instant = Instant .now().truncatedTo(ChronoUnit .SECONDS )
1424
@@ -36,6 +46,82 @@ fun Long.toLocalizedTimestamp(): String {
3646 return formatter.format(Date (this ))
3747}
3848
49+ fun getDaysInMonth (month : LocalDate ): List <LocalDate ?> {
50+ val firstDayOfMonth = LocalDate (month.year, month.month, CalendarConstants .FIRST_DAY_OF_MONTH )
51+ val daysInMonth = month.month.length(isLeapYear(month.year))
52+
53+ // Get the day of week for the first day (1 = Monday, 7 = Sunday)
54+ val firstDayOfWeek = firstDayOfMonth.dayOfWeek.ordinal + CalendarConstants .CALENDAR_WEEK_OFFSET
55+
56+ // Calculate offset (days before the first day)
57+ // We want Sunday to be 0, so adjust accordingly
58+ val offset = (firstDayOfWeek % CalendarConstants .DAYS_IN_WEEK_MOD )
59+
60+ val days = mutableListOf<LocalDate ?>()
61+
62+ // Add empty spaces before the first day
63+ repeat(offset) {
64+ days.add(null )
65+ }
66+
67+ // Add all days of the month
68+ for (day in CalendarConstants .FIRST_DAY_OF_MONTH .. daysInMonth) {
69+ days.add(LocalDate (month.year, month.month, day))
70+ }
71+
72+ // Add empty spaces to complete the last week (total should be multiple of 7)
73+ while (days.size % CalendarConstants .DAYS_IN_WEEK_MOD != 0 ) {
74+ days.add(null )
75+ }
76+
77+ return days
78+ }
79+
80+ fun isLeapYear (year : Int ): Boolean {
81+ return (year % CalendarConstants .LEAP_YEAR_DIVISOR_4 == 0 && year % CalendarConstants .LEAP_YEAR_DIVISOR_100 != 0 ) ||
82+ (year % CalendarConstants .LEAP_YEAR_DIVISOR_400 == 0 )
83+ }
84+
85+ fun isDateInRange (dateMillis : Long , startMillis : Long? , endMillis : Long? ): Boolean {
86+ if (startMillis == null ) return false
87+ val end = endMillis ? : startMillis
88+
89+ val normalizedDate = kotlinx.datetime.Instant .fromEpochMilliseconds(dateMillis)
90+ .toLocalDateTime(TimeZone .currentSystemDefault()).date
91+ val normalizedStart = kotlinx.datetime.Instant .fromEpochMilliseconds(startMillis)
92+ .toLocalDateTime(TimeZone .currentSystemDefault()).date
93+ val normalizedEnd = kotlinx.datetime.Instant .fromEpochMilliseconds(end)
94+ .toLocalDateTime(TimeZone .currentSystemDefault()).date
95+
96+ return normalizedDate >= normalizedStart && normalizedDate <= normalizedEnd
97+ }
98+
99+ fun LocalDate.toMonthYearString (): String {
100+ val formatter = SimpleDateFormat (DatePattern .MONTH_YEAR_FORMAT , Locale .getDefault())
101+ val calendar = Calendar .getInstance()
102+ calendar.set(year, monthNumber - CalendarConstants .MONTH_INDEX_OFFSET , CalendarConstants .FIRST_DAY_OF_MONTH )
103+ return formatter.format(calendar.time)
104+ }
105+
106+ fun LocalDate.minusMonths (months : Int ): LocalDate =
107+ this .toJavaLocalDate()
108+ .minusMonths(months.toLong())
109+ .withDayOfMonth(1 ) // Always use first day of month for display
110+ .toKotlinLocalDate()
111+
112+ fun LocalDate.plusMonths (months : Int ): LocalDate =
113+ this .toJavaLocalDate()
114+ .plusMonths(months.toLong())
115+ .withDayOfMonth(1 ) // Always use first day of month for display
116+ .toKotlinLocalDate()
117+
118+ fun LocalDate.endOfDay (): Long {
119+ return this .atStartOfDayIn(TimeZone .currentSystemDefault())
120+ .plus(1 .days)
121+ .minus(1 .milliseconds)
122+ .toEpochMilliseconds()
123+ }
124+
39125object DatePattern {
40126 const val DATE_TIME = " dd/MM/yyyy, HH:mm"
41127 const val INVOICE_EXPIRY = " MMM dd, h:mm a"
@@ -45,4 +131,32 @@ object DatePattern {
45131 const val ACTIVITY_TIME = " h:mm"
46132 const val LOG_FILE = " yyyy-MM-dd_HH-mm-ss"
47133 const val CHANNEL_DETAILS = " MMM d, yyyy, HH:mm"
134+
135+ const val MONTH_YEAR_FORMAT = " MMMM yyyy"
136+ const val DATE_FORMAT = " MMM d, yyyy"
137+ const val WEEKDAY_FORMAT = " EEE"
48138}
139+
140+ object CalendarConstants {
141+
142+ // Calendar grid
143+ const val DAYS_IN_WEEK = 7
144+ const val FIRST_DAY_OF_MONTH = 1
145+
146+ // Date formatting
147+ const val WEEKDAY_ABBREVIATION_LENGTH = 3
148+
149+ // Calendar math
150+ const val DAYS_IN_WEEK_MOD = 7
151+ const val CALENDAR_WEEK_OFFSET = 1
152+ const val MONTH_INDEX_OFFSET = 1
153+
154+ // Leap year calculation
155+ const val LEAP_YEAR_DIVISOR_4 = 4
156+ const val LEAP_YEAR_DIVISOR_100 = 100
157+ const val LEAP_YEAR_DIVISOR_400 = 400
158+
159+ // Preview
160+ const val PREVIEW_DAYS_AGO = 7
161+ }
162+
0 commit comments