validar - formato de fecha en android studio
Cómo enmascarar un EditText para mostrar el formato de fecha dd/mm/aaaa (5)
¿Cómo puedo formatear un EditText
para seguir el formato " dd/mm/yyyy
" de la misma manera que podemos formatear usando un TextWatcher
para enmascarar la entrada del usuario para que parezca "0.05 €". No estoy hablando de limitar los caracteres o validar una fecha, simplemente enmascarar al formato anterior.
Escribí este TextWatcher
para un proyecto, espero que sea útil para alguien. Tenga en cuenta que no valida la fecha ingresada por el usuario, y debe manejar eso cuando el foco cambie, ya que el usuario puede no haber terminado de ingresar la fecha.
Actualización 25/06 Hizo una wiki para ver si alcanzamos un mejor código final.
Actualización 07/06 Finalmente agregué algún tipo de validación al observador mismo. Hará lo siguiente con fechas inválidas:
- Si el mes es mayor a 12, será 12 (diciembre)
- Si la fecha es mayor que la del mes seleccionado, márcala como máximo para ese mes.
- Si el año no está en el rango
1900-2100
, cámbielo para que esté dentro del rango
Esta validación se ajusta a mis necesidades, pero algunos de ustedes querrán cambiarla un poco, los rangos son fácilmente modificables y podría conectar estas validaciones al mensaje Toast
, por ejemplo, para notificar al usuario que hemos modificado su fecha, ya que fue inválido
En este código, EditText
que tenemos una referencia a nuestra date
llamada EditText
que tiene este TextWatcher
adjunto, esto se puede hacer de la siguiente manera:
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();
Cuando el usuario cambia el texto de 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());
}
}
También implementamos las otras dos funciones porque tenemos que
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void afterTextChanged(Editable s) {}
};
Esto produce el siguiente efecto, donde borrar o insertar caracteres revelará u ocultará la máscara dd/mm/yyyy
. Debería ser fácil de modificar para adaptarse a otras máscaras de formato ya que traté de dejar el código lo más simple posible.
Esta respuesta no aplica una máscara completa para los dígitos restantes sin tipo. Sin embargo, está relacionado y es la solución que necesitaba. Funciona de manera similar a cómo funciona PhoneNumberFormattingTextWatcher
.
A medida que escribe, agrega barras para separar una fecha formateada como mm/dd/yyyy
. No hace ninguna validación, solo formateo.
No hay necesidad de una referencia de EditText
. Solo configura al oyente y funciona. myEditText.addTextChangedListener(new DateTextWatcher());
import android.text.Editable;
import android.text.TextWatcher;
import java.util.Locale;
/**
* Adds slashes to a date so that it matches mm/dd/yyyy.
*
* Created by Mark Miller on 12/4/17.
*/
public class DateTextWatcher implements TextWatcher {
public static final int MAX_FORMAT_LENGTH = 8;
public static final int MIN_FORMAT_LENGTH = 3;
private String updatedText;
private boolean editing;
@Override
public void beforeTextChanged(CharSequence charSequence, int start, int before, int count) {
}
@Override
public void onTextChanged(CharSequence text, int start, int before, int count) {
if (text.toString().equals(updatedText) || editing) return;
String digitsOnly = text.toString().replaceAll("//D", "");
int digitLen = digitsOnly.length();
if (digitLen < MIN_FORMAT_LENGTH || digitLen > MAX_FORMAT_LENGTH) {
updatedText = digitsOnly;
return;
}
if (digitLen <= 4) {
String month = digitsOnly.substring(0, 2);
String day = digitsOnly.substring(2);
updatedText = String.format(Locale.US, "%s/%s", month, day);
}
else {
String month = digitsOnly.substring(0, 2);
String day = digitsOnly.substring(2, 4);
String year = digitsOnly.substring(4);
updatedText = String.format(Locale.US, "%s/%s/%s", month, day, year);
}
}
@Override
public void afterTextChanged(Editable editable) {
if (editing) return;
editing = true;
editable.clear();
editable.insert(0, updatedText);
editing = false;
}
}
Intente utilizar una biblioteca que resuelva este problema, ya que el enmascarado no está disponible de fábrica. Hay muchos casos de esquina (como agregar / eliminar caracteres en el medio del texto ya enmascarado) y para manejarlo correctamente terminarás con un montón de código (y errores).
Aquí hay algunas bibliotecas disponibles:
https://github.com/egslava/edittext-mask
https://github.com/dimitar-zabaznoski/MaskedEditText
https://github.com/pinball83/Masked-Edittext
https://github.com/RedMadRobot/input-mask-android
https://github.com/santalu/mask-edittext
** Tenga en cuenta que al momento de escribir estas bibliotecas no están exentas de problemas, por lo que es su responsabilidad elegir cuál le queda mejor y probar el código.
La respuesta actual es muy buena y me ayudó a guiarme hacia mi propia solución. Hay algunas razones por las que decidí publicar mi propia solución a pesar de que esta pregunta ya tiene una respuesta válida:
- Estoy trabajando en Kotlin, no en Java. Las personas que se encuentran con el mismo problema tendrán que traducir la solución actual.
- Quería escribir una respuesta que fuera más legible para que las personas puedan adaptarla más fácilmente a sus propios problemas.
- Como lo sugirió dengue8830, encapsulé la solución a este problema en una clase, para que cualquiera pueda usarla sin siquiera preocuparse por la implementación.
Para usarlo, simplemente haz algo como:
- DateInputMask (mEditText) .listen ()
Y la solución se muestra a continuación:
class DateInputMask(val input : EditText) {
fun listen() {
input.addTextChangedListener(mDateEntryWatcher)
}
private val mDateEntryWatcher = object : TextWatcher {
var edited = false
val dividerCharacter = "/"
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
if (edited) {
edited = false
return
}
var working = getEditText()
working = manageDateDivider(working, 2, start, before)
working = manageDateDivider(working, 5, start, before)
edited = true
input.setText(working)
input.setSelection(input.text.length)
}
private fun manageDateDivider(working: String, position : Int, start: Int, before: Int) : String{
if (working.length == position) {
return if (before <= position && start < position)
working + dividerCharacter
else
working.dropLast(1)
}
return working
}
private fun getEditText() : String {
return if (input.text.length >= 10)
input.text.toString().substring(0,10)
else
input.text.toString()
}
override fun afterTextChanged(s: Editable) {}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
}
}
una forma más limpia de usar el código de Juan Cortés es ponerlo en una clase:
public class DateInputMask implements TextWatcher {
private String current = "";
private String ddmmyyyy = "DDMMYYYY";
private Calendar cal = Calendar.getInstance();
private EditText input;
public DateInputMask(EditText input) {
this.input = input;
this.input.addTextChangedListener(this);
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (s.toString().equals(current)) {
return;
}
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;
input.setText(current);
input.setSelection(sel < current.length() ? sel : current.length());
}
@Override
public void afterTextChanged(Editable s) {
}
}
entonces puedes reutilizarlo
new DateInputMask(myEditTextInstance);