How to mask an EditText to show the dd/mm/yyyy date format

Juan Cortés picture Juan Cortés · Jun 3, 2013 · Viewed 75.1k times · Source

How can I format an EditText to follow the "dd/mm/yyyy" format the same way that we can format using a TextWatcher to mask the user input to look like "0.05€". I'm not talking about limiting the characters, or validating a date, just masking to the previous format.

Answer

Juan Cortés picture Juan Cortés · Jun 3, 2013

I wrote this TextWatcher for a project, hopefully it will be helpful to someone. Note that it does not validate the date entered by the user, and you should handle that when the focus changes, since the user may not have finished entering the date.

Update 25/06 Made it a wiki to see if we reach a better final code.

Update 07/06 I finally added some sort of validation to the watcher itself. It will do the following with invalid dates:

  • If the month is greater than 12, it will be 12 (December)
  • If the date is greater than the one for the month selected, make it the max for that month.
  • If the year is not in the range 1900-2100, change it to be in the range

This validation fits my needs, but some of you may want to change it a little bit, ranges are easily changeable and you could hook this validations to Toast message for instance, to notify the user that we've modified his/her date since it was invalid.

In this code, I will be assuming that we have a reference to our EditText called date that has this TextWatcher attached to it, this can be done something like this:

EditText date;
date = (EditText)findViewById(R.id.whichdate);
date.addTextChangedListener(tw);

TextWatcher tw = new TextWatcher() {
    private String current = "";
    private String ddmmyyyy = "DDMMYYYY";
    private Calendar cal = Calendar.getInstance();

When user changes text of the EditText

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        if (!s.toString().equals(current)) {
            String clean = s.toString().replaceAll("[^\\d.]|\\.", "");
            String cleanC = current.replaceAll("[^\\d.]|\\.", "");

            int cl = clean.length();
            int sel = cl;
            for (int i = 2; i <= cl && i < 6; i += 2) {
                sel++;
            }
            //Fix for pressing delete next to a forward slash
            if (clean.equals(cleanC)) sel--;

            if (clean.length() < 8){
               clean = clean + ddmmyyyy.substring(clean.length());
            }else{
               //This part makes sure that when we finish entering numbers
               //the date is correct, fixing it otherwise
               int day  = Integer.parseInt(clean.substring(0,2));
               int mon  = Integer.parseInt(clean.substring(2,4));
               int year = Integer.parseInt(clean.substring(4,8));

               mon = mon < 1 ? 1 : mon > 12 ? 12 : mon;
               cal.set(Calendar.MONTH, mon-1);
               year = (year<1900)?1900:(year>2100)?2100:year;
               cal.set(Calendar.YEAR, year); 
               // ^ first set year for the line below to work correctly
               //with leap years - otherwise, date e.g. 29/02/2012
               //would be automatically corrected to 28/02/2012 

               day = (day > cal.getActualMaximum(Calendar.DATE))? cal.getActualMaximum(Calendar.DATE):day;
               clean = String.format("%02d%02d%02d",day, mon, year);
            }

            clean = String.format("%s/%s/%s", clean.substring(0, 2),
                clean.substring(2, 4),
                clean.substring(4, 8));

            sel = sel < 0 ? 0 : sel;
            current = clean;
            date.setText(current);
            date.setSelection(sel < current.length() ? sel : current.length());
        }
    }

We also implement the other two functions because we have to

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {}

    @Override
    public void afterTextChanged(Editable s) {}
};

This produces the following effect, where deleting or inserting characters will reveal or hide the dd/mm/yyyy mask. It should be easy to modify to fit other format masks since I tried to leave the code as simple as possible.

enter image description here