Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: [ANDROAPP-6271] crash when date format make year out of range #269

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import androidx.compose.material3.DatePicker
import androidx.compose.material3.DatePickerColors
import androidx.compose.material3.DatePickerDefaults
import androidx.compose.material3.DatePickerDialog
import androidx.compose.material3.DatePickerState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
Expand Down Expand Up @@ -238,27 +239,7 @@ fun InputDateTime(
},
inputStyle = uiModel.inputStyle,
)
var datePickerState = rememberDatePickerState()
if (!uiModel.inputTextFieldValue?.text.isNullOrEmpty() && uiModel.actionType != DateTimeActionType.TIME) {
datePickerState = if (uiModel.actionType == DateTimeActionType.DATE_TIME && uiModel.inputTextFieldValue?.text?.length == 12 && yearIsInRange(uiModel.inputTextFieldValue.text.substring(0, 8), uiModel.yearRange)) {
rememberDatePickerState(
initialSelectedDateMillis = parseStringDateToMillis(
uiModel.inputTextFieldValue.text.substring(0, uiModel.inputTextFieldValue.text.length - 4),
pattern = uiModel.format,
),
yearRange = uiModel.yearRange,
)
} else {
if (uiModel.inputTextFieldValue?.text?.length == 8 && yearIsInRange(uiModel.inputTextFieldValue.text, uiModel.yearRange)) {
rememberDatePickerState(
initialSelectedDateMillis = parseStringDateToMillis(uiModel.inputTextFieldValue.text, uiModel.format),
yearRange = uiModel.yearRange,
)
} else {
datePickerState
}
}
}
val datePickerState = provideDatePickerState(uiModel)

if (showDatePicker) {
MaterialTheme(
Expand Down Expand Up @@ -418,7 +399,7 @@ fun getSupportingTextList(uiModel: InputDateTimeModel, dateOutOfRangeItem: Suppo
),
uiModel.selectableDates, uiModel.format,
)
dateIsInYearRange = yearIsInRange(uiModel.inputTextFieldValue.text.substring(0, uiModel.inputTextFieldValue.text.length - 4), uiModel.yearRange)
dateIsInYearRange = yearIsInRange(uiModel.inputTextFieldValue.text, getDefaultFormat(uiModel.actionType), uiModel.yearRange)
isValidHourFormat = isValidHourFormat(uiModel.inputTextFieldValue.text.substring(8, 12))
if (!dateIsInRange || !dateIsInYearRange) supportingTextList.add(dateOutOfRangeItem)
if (!isValidHourFormat) supportingTextList.add(incorrectHourFormatItem)
Expand All @@ -427,7 +408,7 @@ fun getSupportingTextList(uiModel: InputDateTimeModel, dateOutOfRangeItem: Suppo
DateTimeActionType.DATE -> {
if (uiModel.inputTextFieldValue?.text!!.length == 8) {
dateIsInRange = dateIsInRange(parseStringDateToMillis(uiModel.inputTextFieldValue.text), uiModel.selectableDates, uiModel.format)
dateIsInYearRange = yearIsInRange(uiModel.inputTextFieldValue.text, uiModel.yearRange)
dateIsInYearRange = yearIsInRange(uiModel.inputTextFieldValue.text, getDefaultFormat(uiModel.actionType), uiModel.yearRange)
if (!dateIsInRange || !dateIsInYearRange) supportingTextList.add(dateOutOfRangeItem)
}
}
Expand All @@ -436,6 +417,31 @@ fun getSupportingTextList(uiModel: InputDateTimeModel, dateOutOfRangeItem: Suppo
return supportingTextList.toList()
}

@Composable
@OptIn(ExperimentalMaterial3Api::class)
private fun provideDatePickerState(uiModel: InputDateTimeModel): DatePickerState {
return uiModel.inputTextFieldValue?.text?.takeIf {
it.isNotEmpty() &&
yearIsInRange(it, getDefaultFormat(uiModel.actionType), uiModel.yearRange)
}?.let {
rememberDatePickerState(
initialSelectedDateMillis = parseStringDateToMillis(
dateString = it,
pattern = getDefaultFormat(uiModel.actionType),
),
yearRange = uiModel.yearRange,
)
} ?: rememberDatePickerState()
}

private fun getDefaultFormat(actionType: DateTimeActionType): String {
return when (actionType) {
DateTimeActionType.DATE -> "ddMMyyyy"
DateTimeActionType.TIME -> "HHmm"
DateTimeActionType.DATE_TIME -> "ddMMyyyyHHmm"
}
}

enum class DateTimeActionType {
DATE, TIME, DATE_TIME
}
Expand Down Expand Up @@ -537,16 +543,12 @@ private fun getTime(timePickerState: TimePickerState, format: String? = "HHmm"):
return formater.format(cal.time)
}

fun parseStringDateToMillis(dateString: String, pattern: String = "ddMMyyyy", locale: Locale = Locale.getDefault()): Long {
return if (dateString.isNotEmpty()) {
val cal = Calendar.getInstance()
val sdf = SimpleDateFormat(pattern, locale)
sdf.timeZone = TimeZone.getTimeZone("UTC")
cal.time = sdf.parse(dateString) ?: Date()
fun parseStringDateToMillis(dateString: String, pattern: String = "ddMMyyyy"): Long {
val cal = Calendar.getInstance()
return dateString.parseDate(pattern)?.let {
cal.time = it
cal.timeInMillis
} else {
0L
}
} ?: 0L
}

data class SelectableDates(
Expand Down Expand Up @@ -597,10 +599,22 @@ internal fun dateIsInRange(date: Long, allowedDates: SelectableDates, format: St
)
}

fun yearIsInRange(date: String, yearRange: IntRange): Boolean {
return (
yearRange.contains(date.substring(date.length - 4, date.length).toInt())
)
fun yearIsInRange(date: String, pattern: String, yearRange: IntRange): Boolean {
val cal = Calendar.getInstance()
return date.parseDate(pattern)?.let {
cal.time = it
yearRange.contains(cal.get(Calendar.YEAR))
} ?: false
}

fun String.parseDate(pattern: String): Date? {
return if (isNotEmpty() && length == pattern.length) {
val sdf = SimpleDateFormat(pattern, Locale.getDefault())
sdf.timeZone = TimeZone.getTimeZone("UTC")
sdf.parse(this)
} else {
null
}
}

@OptIn(ExperimentalMaterial3Api::class)
Expand Down
Loading