'How to write regex to identify different date patterns of a String in android?

I receive different date patterns in APIs. Is there any way to identify the date pattern of the string received in an API so that I can format them without throwing any date exceptions?



Solution 1:[1]

Use this String extension function to identify the date pattern of the String.

/**
 * Created By Sweta 
 *  *  Method to check Input Date Pattern Before before Formatting
 * @param outputPattern This parameter takes the output DATE Pattern as String
 *  Checks four patterns: "dd/mm/yyyy", "mm/dd/yyyy hh:MM:ss", "dd MMM yyyy", "yyyy-MM-dd",
 *  "yyyy-MM-dd'T'HH:mm:ss", "dd-MMM-yyyy", "MM/dd/yyyy hh:mm:ss a", "dd-mm-yyyy hh:mm:ss",
 *  "dd-mm-yyyy hh:mm:ss a", "mm/dd/yyyy hh:mm:ss", "mm/dd/yyyy"
 */
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
fun String.checkInputDatePatternBefore(outputPattern: String): String {

    //"dd/mm/yyyy"
    val regex1 = "^(3[01]|[12][0-9]|0[1-9]|[1-9])/(1[0-2]|0[1-9]|[1-9])/[0-9]{4}$".toRegex()
    
    //"mm/dd/yyyy hh:MM:ss"
    val regex2 =
        "^(1[0-2]|0[1-9]|[1-9])/(3[01]|[12][0-9]|0[1-9]|[1-9])/[0-9]{4} [0-9][0-9]:([0]?[0-5] 
    [0-9]|[0-9]):([0-5][0-9])$"
            .toRegex()

    //"dd MMM yyyy"
    val regex3 = "(3[01]|[12][0-9]|0[1-9]|[1-9])[\\s][a-zA-Z]{3}[\\s][0-9]{4}$".toRegex()
   
    //"yyyy-MM-dd"
    val regex4 =
        "([0-9]{4})[\\-](1[0-2]|0[1-9]|[1-9])[\\-](3[01]|[12][0-9]|0[1-9]|[1-9])$".toRegex()
    //"yyyy-MM-dd'T'HH:mm:ss"
   
   val regex5 =
        "([0-9]{4})[\\-](1[0-2]|0[1-9]|[1-9])[\\-](3[01]|[12][0-9]|0[1-9])[T][0-9][0-9]:([0]? 
   [0-5][0-9]|[0-9]):([0-5][0-9])$".toRegex()

    //"dd-MMM-yyyy"
    val regex6 = "(3[01]|[12][0-9]|0[1-9]|[1-9])[\\-][a-zA-Z]{3}[\\-][0-9]{4}$".toRegex()

    //"MM/dd/yyyy hh:mm:ss a"
    val regex7 =
        "^((1[0-2]|0[1-9]|[1-9])/3[01]|[12][0-9]|0[1-9]|[1-9])/[0-9]{4}[\\s][0-9][0-9]:([0]? 
   [0-5][0-9]|[0-9]):([0-5][0-9])[\\s][a-zA-Z]{2}$"
            .toRegex()

    //28-02-2022 12:12:24
    //"dd-mm-yyyy hh:mm:ss"
    val regex8 =
        "(3[01]|[12][0-9]|0[1-9]|[1-9])[\\-](1[0-2]|0[1-9]|[1-9])[\\-][0-9]{4}[\\s][0-9][0-9]: 
  ([0]?[0-5][0-9]|[0-9]):([0-5][0-9])$".toRegex()
  
   //"dd-mm-yyyy hh:mm:ss a"
    val regex9 =
        "(3[01]|[12][0-9]|0[1-9]|[1-9])[\\-](1[0-2]|0[1-9]|[1-9])[\\-][0-9]{4}[\\s][0-9][0-9]: 
   ([0]?[0-5][0-9]|[0-9]):([0-5][0-9])[\\s][a-zA-Z]{2}$".toRegex()
  
   //"mm/dd/yyyy hh:mm:ss"
    val regex10 =
        "(1[0-2]|0[1-9]|[1-9])[/](3[01]|[12][0-9]|0[1-9]|[1-9])[/][0-9]{4}[\\s][0-9][0-9]: 
   ([0]?[0-5][0-9]|[0-9]):([0-5][0-9])$"
            .toRegex()
   
   //"mm/dd/yyyy"
    val regex11 =
        "(1[0-2]|0[1-9]|[1-9])[/](3[01]|[12][0-9]|0[1-9]|[1-9])[/][0-9]{4}$"
            .toRegex()


    val bool1: Boolean = regex1.matches(this)
    val bool2: Boolean = regex2.matches(this)
    val bool3: Boolean = regex3.matches(this)
    val bool4: Boolean = regex4.matches(this)
    val bool5: Boolean = regex5.matches(this)
    val bool6: Boolean = regex6.matches(this)
    val bool7: Boolean = regex7.matches(this)
    val bool8: Boolean = regex8.matches(this)
    val bool9: Boolean = regex9.matches(this)
    val bool10: Boolean = regex10.matches(this)
    val bool11: Boolean = regex11.matches(this)

    when {
        bool1 -> {
            Log.d(TAG, "Pattern dd/mm/yyyy")
            return "Pattern dd/mm/yyyy"
        }
        bool2 -> {
            Log.d(TAG, "Pattern mm/dd/yyyy hh:MM:ss")
            return "Pattern mm/dd/yyyy hh:MM:ss"
        }
        bool3 -> {
            Log.d(TAG, "Pattern dd MMM yyyy")
            return "Pattern dd MMM yyyy"
        }
        bool4 -> {
            Log.d(TAG, "Pattern yyyy-MM-dd")
            return "Pattern yyyy-MM-dd"
        }
        bool5 -> {
            Log.d(TAG, "Pattern yyyy-MM-dd'T'HH:mm:ss")
            return "Pattern yyyy-MM-dd'T'HH:mm:ss"
        }
        bool6 -> {
            Log.d(TAG, "Pattern dd-MMM-yyyy")
            return "Pattern dd-MMM-yyyy"
        }
        bool7 -> {
            Log.d(TAG, "Pattern MM/dd/yyyy hh:mm:ss a")
            return "Pattern MM/dd/yyyy hh:mm:ss a"
        }
        bool8 -> {
            Log.d(TAG, "Pattern dd-mm-yyyy hh:mm:ss")
            return "Pattern dd-mm-yyyy hh:mm:ss"
        }
        bool9 -> {
            Log.d(TAG, "Pattern dd-mm-yyyy hh:mm:ss AM/PM")
            return "Pattern dd-mm-yyyy hh:mm:ss AM/PM"
        }
        bool10 -> {
            Log.d(TAG, "Pattern mm/dd/yyyy hh:mm:ss")
            return "Pattern mm/dd/yyyy hh:mm:ss"
        }
        bool11 -> {
            Log.d(TAG, "Pattern mm/dd/yyyy")
            return "Pattern mm/dd/yyyy"
        }
        else -> {
            Log.d(TAG, "Date pattern not recognized!")
            Log.d(TAG, this)
            return "Date pattern not recognized!"
        }
    }
}

Solution 2:[2]

Providing an alternate implementation of the OP's answer. Code Review time! (Not including the correctness of this method or the regexes.)

The repetitive nature of the bulk of the code should have been a hint that it needs to be refactored. Everything other than the regex definitions are just repeated blocks, like:

val bool1: Boolean = regex1.matches(this)
val bool2: Boolean = regex2.matches(this)
...
val bool11: Boolean = regex11.matches(this)

and also

when {
    bool1 -> {
        Log.d(TAG, "Pattern dd/mm/yyyy")
        return "Pattern dd/mm/yyyy"
    }
    bool2 -> {
        Log.d(TAG, "Pattern mm/dd/yyyy hh:MM:ss")
        return "Pattern mm/dd/yyyy hh:MM:ss"
    }
    ...
}

The form is identical but with different variables and messages. And as per the current logic, stops at the first match. If a new regex is added, you need to add more val bool12/13/14/15 and more checks in when.

Instead, put all that into a collection, like a List. And associate the date format with the regex, in a Map. Once these two changes are made, the only thing left is the regexes which must be defined. You can also put each pattern name and the regex into a data class with the related methods.

/* label is the date format and pattern is the regex for that format.
*  result() provides a message (string) when trying to match, but can be
*  modified to get a bool result, like in boolResult(). */
data class DatePatternChecker(val label: String, val pattern: Regex) {
    fun result(dateString: String): String? = if (pattern.matches(dateString)) "Pattern is $label" else null
    fun boolResult(dateString: String): Boolean = pattern.matches(dateString)
    constructor(labelPattern: Map.Entry<String, Regex>) : this(labelPattern.key, labelPattern.value)
}

val datePatternChecks = mapOf(
    "dd/mm/yyyy" to
            "^(3[01]|[12][0-9]|0[1-9]|[1-9])/(1[0-2]|0[1-9]|[1-9])/[0-9]{4}$".toRegex(),
    "mm/dd/yyyy hh:MM:ss" to
            "^(1[0-2]|0[1-9]|[1-9])/(3[01]|[12][0-9]|0[1-9]|[1-9])/[0-9]{4} [0-9][0-9]:([0]?[0-5][0-9]|[0-9]):([0-5][0-9])$".toRegex(),
    // etc.
).map(::DatePatternChecker)

fun String.datePattern(): String = datePatternChecks
    .firstNotNullOfOrNull { it.result(this) }
    ?: "Date pattern not recognized!"

// better
fun String.datePatternByBool(): String = datePatternChecks
    .firstOrNull { it.boolResult(this) }
    ?.run { "Pattern is $label" }
    ?: "Date pattern not recognized!"
// using datePattern
println("21/01/2001".datePattern())           // Pattern is dd/mm/yyyy
println("12/31/2001 13:55:59".datePattern())  // Pattern is mm/dd/yyyy hh:MM:ss
println("99/32/2001".datePattern())           // Date pattern not recognized!
// using datePatternByBool
println("21/01/2001".datePatternByBool())           // Pattern is dd/mm/yyyy
println("12/31/2001 13:55:59".datePatternByBool())  // Pattern is mm/dd/yyyy hh:MM:ss
println("99/32/2001".datePatternByBool())           // Date pattern not recognized!

This is more functional and has no repetition. If a new pattern or rule needs to be added, just put it into datePatternChecks at the correct position/order; no other change needed to use the new pattern.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Sweta Jain
Solution 2 aneroid