Elegant way for parsing birthday from rodne cislo (czech republic ID) How can I refactor it to elegant way? This code is about parse birthday from Rodne Cislo string. I don't know how to do it clever and add more clean code style details

public static void extractBirthdayFromRodneCislo(String rodneCislo) {
    rodneCislo = rodneCislo.replace("/", "=");

    int year;
    int month;
    int day;

    if (rodneCislo.length() &lt; 10) {
        year = Integer.parseInt("19" + rodneCislo.charAt(0) + rodneCislo.charAt(1));
    } else {
        String century = Integer.parseInt(String.valueOf(rodneCislo.charAt(0)) + String.valueOf(rodneCislo.charAt(1))) &lt; 53 ? "20" : "19";
        year = Integer.parseInt(century + rodneCislo.charAt(0) + rodneCislo.charAt(1));
    }

    month = Integer.parseInt(String.valueOf(rodneCislo.charAt(2)) + String.valueOf(rodneCislo.charAt(3)));

    if (month &gt; 12) {
        month -= 50;
    }
    day = Integer.parseInt(String.valueOf(rodneCislo.charAt(4)) + rodneCislo.charAt(5));

    System.out.println(year + " " + month + " " + day);
}


The form is YYXXDD/SSSC, where XX=MM (month of birth) for male (numbers 01-12) and XX=MM+50 for female (numbers 51-62), SSS is a serial number separating persons born on the same date and C is a check digit, but for people born before 1 January 1954 the form is without the check digit - YYXXDD/SSS. This enables the system to work until the year 2054. The whole number must be divisible by 11.
In addition to Doi9t's answer, which gives good advice, I'll add that your function name, extractBirthdayFromRodneCislo, is wrong, as it merely prints the birthday instead extracting it.

The easy fix is to rename the function extractBirthdayFromRodneCislo, which may fit you case, but doesn't allow you to do anything more fancy than that.

The better way to do things would be to return the date, allowing you to do anything with it after calling the method.

I'm completely new to Java, so I might miss something here, but it seems that the java.time.LocalDate datatype is a good fit for the job.

So your function becomes something like this:

public static LocalDate extractBirthdayFromRodneCislo(String rodneCislo) {

    // your logic here

    return LocalDate.of(year, month, day);
}


This approach has the added advantage that, should there be a mistake in the input string leading to an invalid date, it will throw an exception instead of silently accepting an invalid date &ndash; if you pass 622754/1234 to your function or Doi9t's, it will not care that 1962 -23 54 is not an acceptable output and print it just like any other date.

This illustrates to another problem: input validation. Your code doens't care if the input is indeed a Rodne Cislo. It will take any string as input. It may fail if Integer.parseInt encounter non-digit characters, but will accept strings that are too long, too short, has a properly placed / character or not (comments pointed out that Czech people don't always include the /, any other character in this place, etc., leading to a big risk of improperly parsing the string.

One possible tool for validating strings are regular expressions (regex). A lot of different patterns can be used to validate the input string, a simple one would be:

^[0-9]{6}\/[0-9]{3}[0-9]?$


If you are unfamiliar with regex, here is a breakdown of this expression:

^[0-9]{6}\/?[0-9]{3}[0-9]?$

^                                 # start with (no leading characater)
 [0-9]{6}                         # 6 digits
         \/?                      # may or may not include a "/" character
            [0-9]{3}              # 3 additional digits
                    [0-9]?        # 1 optional additional digit
                          $       # ends here (no trailing character)


Checking if the input string matches this pattern will call out a lot of invalid input strings, although it doesn't check if the month and day numbers are within valid ranges &ndash; it is definitely possible, but will get nasty quite quickly, and the LocalDate.of() method already takes care of it.

Another nice thing about regex is that you can extract specific ranges within the expression easily with capture groups. A little modifications on the previous expression gives (with explanation):

^([0-9]{2})([0-9]{2})([0-9]{2})\/?[0-9]{3}[0-9]?$

^                                                     # starts with (no leading characater)
 ([0-9]{2})                                           # 2 digits, capture them in group 1
           ([0-9]{2})                                 # 2 digits, capture them in group 2
                     ([0-9]{2})                       # 2 digits, capture them in group 3
                               \/?                    # an optional "/" character
                                  [0-9]{3}            # 3 digits
                                          ([0-9]?)    # 1 optional digit, capture it in group 4
                                                  $   # ends here (no trailing character)


You can then used the captured strings to parse the numbers (groups 1 to 3), or identify if the birth year is before or after 1954 (group 4).

My final take on the problem is this code:

import java.time.LocalDate;
import java.util.regex.Pattern;
import java.util.regex.Matcher;

class Main {
    public static void main(String[] args) {
        System.out.println(extractBirthdayFromRodneCislo("535223/1234"))
        // &gt; 2053-02-23
        System.out.println(extractBirthdayFromRodneCislo("530223/123"))
        // &gt; 1953-02-23
        System.out.println(extractBirthdayFromRodneCislo("7503121234"))
        // &gt; 1975-03-12
        System.out.println(extractBirthdayFromRodneCislo("753312/1234"))
        // &gt; Throws an exception (invalid month)
        System.out.println(extractBirthdayFromRodneCislo("753312/12345"))
        // &gt; Throws an exception (doesn't match pattern)
    }

    public static LocalDate extractBirthdayFromRodneCislo(String rodneCislo){
        Pattern p = Pattern.compile("^([0-9]{2})([0-9]{2})([0-9]{2})\\/?[0-9]{3}[0-9]?$");

        Matcher m = p.matcher(rodneCislo);

        if (!m.matches()){
            // Input doesn't match the pattern
            // throw a relevant exception
            // although I can't seem to do that propely with Java
        }

        int year = Integer.parseInt(m.group(1));
        if (year &lt; 54 &amp;&amp; !m.group(4).isEmpty()){
          year += 2000;
        }else{
          year += 1900;
        }

        int month = Integer.parseInt(m.group(2));
        if (month &gt; 12){month -= 50;}

        int day = Integer.parseInt(m.group(3));

        return LocalDate.of(year, month, day); // will throw an exception if month or day ends up invalid
    }
}