From e51d9e5a05ebc0b5d5a1d577753293349e01e09d Mon Sep 17 00:00:00 2001 From: Tai Pham Date: Fri, 3 Mar 2023 10:52:27 +0700 Subject: [PATCH] Handle date parsing for 2 digits year (#140) Co-authored-by: Tai Pham --- .../smartscanner/lib/nfc/NFCFragment.kt | 9 +++- .../idpass/smartscanner/lib/nfc/NFCResult.kt | 13 ++++-- .../nfc/passport/PassportDetailsFragment.kt | 3 ++ .../smartscanner/lib/utils/DateUtils.kt | 41 ++++++++++++++++++- 4 files changed, 59 insertions(+), 7 deletions(-) diff --git a/core-lib/src/main/java/org/idpass/smartscanner/lib/nfc/NFCFragment.kt b/core-lib/src/main/java/org/idpass/smartscanner/lib/nfc/NFCFragment.kt index afd41bf8..6effe2d0 100644 --- a/core-lib/src/main/java/org/idpass/smartscanner/lib/nfc/NFCFragment.kt +++ b/core-lib/src/main/java/org/idpass/smartscanner/lib/nfc/NFCFragment.kt @@ -21,6 +21,7 @@ import android.content.Context import android.content.Intent import android.nfc.NfcAdapter import android.nfc.Tag +import android.os.Build import android.os.Bundle import android.os.Handler import android.os.Looper @@ -31,6 +32,7 @@ import android.view.ViewGroup import android.widget.ProgressBar import android.widget.TextView import android.widget.Toast +import androidx.annotation.RequiresApi import androidx.fragment.app.Fragment import io.reactivex.disposables.CompositeDisposable import net.sf.scuba.smartcards.CardServiceException @@ -41,6 +43,8 @@ import org.idpass.smartscanner.lib.nfc.details.NFCDocumentTag import org.idpass.smartscanner.lib.nfc.passport.Passport import org.idpass.smartscanner.lib.scanner.config.Language import org.idpass.smartscanner.lib.utils.DateUtils +import org.idpass.smartscanner.lib.utils.DateUtils.BIRTH_DATE_THRESHOLD +import org.idpass.smartscanner.lib.utils.DateUtils.EXPIRY_DATE_THRESHOLD import org.idpass.smartscanner.lib.utils.DateUtils.formatStandardDate import org.idpass.smartscanner.lib.utils.KeyStoreUtils import org.idpass.smartscanner.lib.utils.LanguageUtils @@ -183,6 +187,7 @@ class NFCFragment : Fragment() { super.onDetach() } + @RequiresApi(Build.VERSION_CODES.O) override fun onResume() { super.onResume() // Display proper language @@ -190,8 +195,8 @@ class NFCFragment : Fragment() { // Display MRZ details textViewNfcTitle?.text = if (label != null) label else getString(R.string.nfc_title) textViewPassportNumber?.text = getString(R.string.doc_number, mrzInfo?.documentNumber) - textViewDateOfBirth?.text = getString(R.string.doc_dob, DateUtils.toAdjustedDate(formatStandardDate(mrzInfo?.dateOfBirth))) - textViewDateOfExpiry?.text = getString(R.string.doc_expiry, DateUtils.toReadableDate(formatStandardDate(mrzInfo?.dateOfExpiry))) + textViewDateOfBirth?.text = getString(R.string.doc_dob, DateUtils.toAdjustedDate(formatStandardDate(mrzInfo?.dateOfBirth, threshold = BIRTH_DATE_THRESHOLD))) + textViewDateOfExpiry?.text = getString(R.string.doc_expiry, DateUtils.toReadableDate(formatStandardDate(mrzInfo?.dateOfExpiry, threshold = EXPIRY_DATE_THRESHOLD))) if (nfcFragmentListener != null) { nfcFragmentListener?.onEnableNfc() diff --git a/core-lib/src/main/java/org/idpass/smartscanner/lib/nfc/NFCResult.kt b/core-lib/src/main/java/org/idpass/smartscanner/lib/nfc/NFCResult.kt index 34643ebf..672bf54f 100644 --- a/core-lib/src/main/java/org/idpass/smartscanner/lib/nfc/NFCResult.kt +++ b/core-lib/src/main/java/org/idpass/smartscanner/lib/nfc/NFCResult.kt @@ -17,9 +17,13 @@ */ package org.idpass.smartscanner.lib.nfc +import android.os.Build +import androidx.annotation.RequiresApi import org.idpass.smartscanner.lib.nfc.passport.Passport import org.idpass.smartscanner.lib.scanner.config.Language.Locale import org.idpass.smartscanner.lib.utils.DateUtils +import org.idpass.smartscanner.lib.utils.DateUtils.BIRTH_DATE_THRESHOLD +import org.idpass.smartscanner.lib.utils.DateUtils.EXPIRY_DATE_THRESHOLD import org.idpass.smartscanner.lib.utils.DateUtils.formatStandardDate import org.idpass.smartscanner.lib.utils.LanguageUtils.isRTL import org.idpass.smartscanner.lib.utils.extension.arrayToString @@ -54,6 +58,7 @@ data class NFCResult( ) { companion object { + @RequiresApi(Build.VERSION_CODES.O) fun formatResult(passport: Passport?, locale: String?, mrzInfo: MRZInfo? = null, image: String? = null, mrzImage: String? = null): NFCResult { val personDetails = passport?.personDetails val additionalPersonDetails = passport?.additionalPersonDetails @@ -102,9 +107,9 @@ data class NFCResult( } } // Get proper date of birth - val dateOfBirth = if (additionalPersonDetails?.fullDateOfBirth.isNullOrEmpty()) { - DateUtils.toAdjustedDate (formatStandardDate(personDetails?.dateOfBirth)) - } else formatStandardDate(additionalPersonDetails?.fullDateOfBirth, "yyyyMMdd") + val dateOfBirth = if (additionalPersonDetails?.fullDateOfBirth.isNullOrEmpty()) { + DateUtils.toAdjustedDate (formatStandardDate(personDetails?.dateOfBirth, threshold = BIRTH_DATE_THRESHOLD)) + } else formatStandardDate(additionalPersonDetails?.fullDateOfBirth, "yyyyMMdd") return NFCResult( image = image, mrzImage = mrzImage, @@ -113,7 +118,7 @@ data class NFCResult( nameOfHolder = additionalPersonDetails?.nameOfHolder, gender = personDetails?.gender?.name, documentNumber = personDetails?.documentNumber, - dateOfExpiry = DateUtils.toReadableDate(formatStandardDate(personDetails?.dateOfExpiry)), + dateOfExpiry = DateUtils.toReadableDate(formatStandardDate(personDetails?.dateOfExpiry, threshold = EXPIRY_DATE_THRESHOLD)), issuingState = personDetails?.issuingState, nationality = personDetails?.nationality, otherNames = additionalPersonDetails?.otherNames?.arrayToString(), diff --git a/core-lib/src/main/java/org/idpass/smartscanner/lib/nfc/passport/PassportDetailsFragment.kt b/core-lib/src/main/java/org/idpass/smartscanner/lib/nfc/passport/PassportDetailsFragment.kt index bde037a1..83c6ded2 100644 --- a/core-lib/src/main/java/org/idpass/smartscanner/lib/nfc/passport/PassportDetailsFragment.kt +++ b/core-lib/src/main/java/org/idpass/smartscanner/lib/nfc/passport/PassportDetailsFragment.kt @@ -20,11 +20,13 @@ package org.idpass.smartscanner.lib.nfc.passport import android.annotation.SuppressLint import android.content.Context import android.graphics.Bitmap +import android.os.Build import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView +import androidx.annotation.RequiresApi import androidx.core.content.ContextCompat import org.idpass.smartscanner.lib.R import org.idpass.smartscanner.lib.databinding.FragmentPassportDetailsBinding @@ -88,6 +90,7 @@ class PassportDetailsFragment : androidx.fragment.app.Fragment() { refreshData(passport) } + @RequiresApi(Build.VERSION_CODES.O) @SuppressLint("SetTextI18n") private fun refreshData(passport: Passport?) { if (passport == null) return diff --git a/core-lib/src/main/java/org/idpass/smartscanner/lib/utils/DateUtils.kt b/core-lib/src/main/java/org/idpass/smartscanner/lib/utils/DateUtils.kt index 2347fbe4..a6966125 100644 --- a/core-lib/src/main/java/org/idpass/smartscanner/lib/utils/DateUtils.kt +++ b/core-lib/src/main/java/org/idpass/smartscanner/lib/utils/DateUtils.kt @@ -18,16 +18,33 @@ package org.idpass.smartscanner.lib.utils import android.annotation.SuppressLint +import android.os.Build +import androidx.annotation.RequiresApi import org.idpass.smartscanner.lib.scanner.config.Language import java.text.DateFormat import java.text.ParseException import java.text.SimpleDateFormat +import java.time.LocalDate import java.util.* object DateUtils { private val dateFormat = SimpleDateFormat("yyyy/MM/dd", Locale.ROOT) + + /** + * DATE THRESHOLD is for the staring year of date parsing yy -> yyyy + * Example: + * With THRESHOLD = 99, start date will be 99 years ago from present year + * PRESENT_YEAR = 2023 + * LOWEST DATE = PRESENT_YEAR - THRESHOLD = 1924 + * HIGHEST DATE = 2023 + * 240101 -> 01/01/1924 + * 230101 -> 01/01/2023 + */ + const val BIRTH_DATE_THRESHOLD = 99 + const val EXPIRY_DATE_THRESHOLD = 49 + fun isValidDate(inDate: String): Boolean { dateFormat.isLenient = false try { @@ -39,12 +56,34 @@ object DateUtils { } fun formatDate(date: Date) : String = dateFormat.format(date) + @RequiresApi(Build.VERSION_CODES.O) @SuppressLint("SimpleDateFormat") - fun formatStandardDate(dateString: String?, fromPattern: String = "yyMMdd", toPattern: String = "MM/dd/yyyy", locale: Locale? = Locale(Language.EN)): String? { + fun formatStandardDate(dateString: String?, fromPattern: String = "yyMMdd", toPattern: String = "MM/dd/yyyy", locale: Locale? = Locale(Language.EN), threshold: Int? = null): String? { + if(fromPattern === "yyMMdd") { + val date = stringToDate2DigitsYear(dateString, threshold) ?: return null + return dateToString(date, SimpleDateFormat(toPattern, locale)) + } + val date = stringToDate(dateString, SimpleDateFormat(fromPattern)) ?: return null return dateToString(date, SimpleDateFormat(toPattern, locale)) } + @RequiresApi(Build.VERSION_CODES.O) + private fun stringToDate2DigitsYear(dateStr: String?, threshold: Int? = null): Date? { + if (dateStr == null || threshold == null) return null + var date: Date? = null + try { + val sdf = SimpleDateFormat("dd/MM/yyyy"); + val startYear = LocalDate.now().minusYears(threshold.toLong()).year + sdf.set2DigitYearStart(sdf.parse("01/01/$startYear")) + sdf.applyPattern("yyMMdd") + date = sdf.parse(dateStr) + } catch (e: ParseException) { + e.printStackTrace() + } + return date + } + private fun stringToDate(dateStr: String?, dateFormat: DateFormat): Date? { if (dateStr == null) return null var date: Date? = null