As I said before here, here and here:
Dates have no format
A date is just a concept, an idea: it represents a specific point in the calendar.
The date of "January 1, 1970" represents this: the specific point of the calendar that corresponds to the 1st of January of 1970.
To express this idea in text form, I can write it in different ways:
- 01/01/1970 (a common format in many countries, including Brazil)
- 1/1/1970 (American format, reversing day and month)
- 1970-01-01 (the format ISO 8601)
- First of January 1970 (in good Portuguese)
- January 1st, 1970 (in English)
- 1970 年 1 月 1 日 (in Japanese)
- and many others...
Note that each of the above formats is different, but all represent the same date (the same numerical values of the day, month and year).
That being said, if you mention that you have "dates in different formats," probably what you have are Strings
containing representations of a date in different formats (or you printed the date and method that displays it is using some specific format). It may seem a little pedantic, but this distinction is important because a String
is a thing, a java.util.Date
is another (the first is a text that can represent a date, the second represents the concept of a point in the timeline, without any notion of format).
If you do not know what format is the String
, the best you can do is try one by one, until you find one that works:
String s = "10/04/2019";
// formatos possíveis
SimpleDateFormat[] formatos = {
new SimpleDateFormat("dd 'de' MMM 'de' yyyy"),
new SimpleDateFormat("dd/MM/yyyy")
};
Date data = null; // java.util.Date
for (SimpleDateFormat sdf : formatos) {
try {
data = sdf.parse(s);
} catch (ParseException e) {
System.out.println("String está no formato inválido, tentar o próximo");
}
}
if (data != null) {
System.out.println(data);
} else {
System.out.println("Não foi possível obter a data");
}
If you want, you can call sdf.setLenient(true);
before the parse
, to ensure that only valid dates are accepted (by default, SimpleDateFormat
accepts values such as January 32, which is automatically adjusted to February 1, in addition to do some pretty weird stuff, depending on the situation - use setLenient(true)
cancels this behavior and only accepts valid dates).
If the name of the month is always in Portuguese, I recommend using a java.util.Locale
in the builder of SimpleDateFormat
:
new SimpleDateFormat("dd 'de' MMM 'de' yyyy", new Locale("pt", "BR"))
If you do not specify a Locale
, he uses the default of the JVM, and it is not always guaranteed to be Portuguese. If you are sure that the name of the month is always in Portuguese, better use the Locale
correspondent.
This is all to turn one String
in a java.util.Date
. Now if you want to display the date in a specific format, you should use a SimpleDateFormat
own, containing the desired format:
SimpleDateFormat formatoSaida = new SimpleDateFormat("dd/MM/yyyy");
System.out.println(formatoSaida.format(data));
The method format
gets a Date
and returns a String
, containing the date in the format indicated.
java.time
From the Level 26 API (required minSdkVersion>=26, it is not enough to have compileSdkVersion>=26), it is possible to use the API java.time
, much better and more modern than Date
and SimpleDateFormat
.
A feature added in this API are optional standards, which makes it possible to Parsing of more than one format:
import java.time.format.DateTimeFormatter;
import java.time.LocalDate;
String s = "10 de abr de 2019";
// formatos possíveis
DateTimeFormatter parser = DateTimeFormatter.ofPattern("[dd/MM/uuuu][dd 'de' MMM 'de' uuuu]", new Locale("pt", "BR"));
LocalDate data = LocalDate.parse(s, parser); // obter a data
// formato de saída
DateTimeFormatter formatoSaida = DateTimeFormatter.ofPattern("dd/MM/uuuu");
System.out.println(formatoSaida.format(data)); // 10/04/2019
In the example above, each format is in brackets, indicating that they are optional. Then the parse
works with both formats, and note that I also use a Locale
corresponding to the Portuguese language because of the name of the month. I also use another DateTimeFormatter
specific to the output format.
Another detail is that in this API there are several different classes to represent dates. In the example above I used LocalDate
, which is a class that only has day, month and year (for that is what the String
possesses).
If you want to work with dates and times, you will have to use a LocalDateTime
(but it will have to set some time in it, since the String
does not have this information). To work with dates and times in a specific time zone, there is the class ZonedDateTime
. For more details, you can consult the oracle tutorial and this question.
For API Level < 26, an alternative is Threeten Backport, an excellent backport of java.time
, that has most classes, methods and functionalities. The difference is that instead of being in the package java.time
, classes are in the package org.threeten.bp
. To use it on Android, follow the instructions of this link (and in this answer there are some other differences between the backport and the java.time
, in the section "Alternatives for Java < 8").
Do not use regex
Despite seem a good idea, use regex to check if a date is valid is not the best solution (I also talk a little bit about it in this answer). Just look at the regex you put to the question: can you understand which points correspond to the day, year and month? Can you change it to accept the names of the months? Besides, she’s using \D
as a separator (i.e., any character other than a digit), so it accepts strings as 30a01a2019
(the letter a
is not a digit - that is, it corresponds to \D
- then regex accepts it as date field separator - see here other examples).
Another point is that date validation involves operations and numerical comparisons: checking whether the day is greater than 31 (or 30, or 28/29, depending on the month), checking whether the year is multiple of 4, or multiple of 400 if it is also multiple of 100 (to know if it is leap and whether I should consider 28 or 29 days for February), etc.
Except that regex works with text, and even the digits are treated as characters, so it can’t do arithmetic operations. Instead, regex has to use alternation, with several possibilities: day 31 with months 1, 3, 5, etc..., or day 30 for months 4, 6, etc... or February 29 for years x, y, z (there are some "tricks" to make the verification of leap years less long), and so on. That’s why regex gets so big, but honestly, it’s not worth using, especially when the native API already has specific classes to deal with the problem.
If a new format appears, for example, just add it to the array SimpleDateFormat
above. But imagine the work to change the regex...
Sqlite
Based on your comment, I did a test on Sqlite (which I confess not to use frequently). I used version 3.28 in my tests.
Generally databases have specific types for dates and times, and all you need to do is save the Date
and ready (no matter the format used, because as already said, dates have no format).
But according to the sqlite documentation, it does not have specific types to handle dates. Instead, you can choose to store them as text (using the format ISO 8601), or as a number, containing the value of timestamp.
In your case, as you seem to be interested only in the day, month and year, I suggest using a text field in ISO 8601 format. It would look something like this:
// manipule a data conforme o que você precisar
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DAY_OF_MONTH, 5);
// grave-a no banco no formato ISO 8601
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String textoQueVaiSerSalvoNoBanco = sdf.format(cal.getTime());
// se estiver usando PreparedStatement
ps.setString(1, textoQueVaiSerSalvoNoBanco);
So you save on the bench like a String
. It will be in ISO 8601 format (year-month-day). To recover it, just get the String
and do the Parsing:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// rs é um ResultSet (caso seja o que você está usando)
Date date = sdf.parse(rs.getString("data_texto"));
Once having the Date
, you can do whatever you want with it. You can set it in a Calendar
to manipulate him:
Calendar c = Calendar.getInstance();
c.setTime(date); // setar o Date
// manipular a data conforme necessário
c.add(Calendar.DAY_OF_MONTH, 2);
Or even show it in another format:
// mostrar o Date em outro formato
SimpleDateFormat formatoSaida = new SimpleDateFormat("dd/MM/yyyy");
System.out.println(formatoSaida.format(date));
The same goes for the java.time
(if you are using API Level >= 26). To manipulate the date, use a LocalDate
:
// data atual mais 5 dias
LocalDate data = LocalDate.now().plusDays(5);
ps.setString(1, date.toString());
The advantage of LocalDate
is that your method toString()
already returns the date in ISO 8601 format. In addition, this class can also do Parsing of a String
in this format directly, so getting the text from the database and turning it into date is also easy:
LocalDate date = LocalDate.parse(rs.getString("data_texto"));
To display this date in another format, then you need a DateTimeFormatter
:
// mostrar o LocalDate em outro formato
DateTimeFormatter formatoSaida = DateTimeFormatter.ofPattern("dd/MM/uuuu");
System.out.println(formatoSaida.format(date));
How are you getting the system date? What you want is to have a string that represents this date always in the same format, regardless of the device/user configuration?
– ramaral
Exactly @ramaral, I get the date and turn it into a String, write it in the database and retrieve this String later, and as suggested in the answers below I already use the necessary Imports for my application to 'see' where I want this date: import java.util.Calendar; import java.util.Date; import java.util.Locale; Calendar myCal = Calendar.getInstance(); myCal.add(Calendar.DAY_OF_MONTH, 5); // adds 5 days on date final String cts = Dateformat.getDateInstance(). format(myCal.getTime()); maybe here I am passing the wrong arguments
– user3873165
Basically I get an initial date Dateformat.getDateInstance(). format(new date(); and transform to get what I call "return" calendar.getInstance(); myCal.add(Calendar.DAY_OF_MONTH, 5);
– user3873165
getDateInstance()
uses the default locale-based format and this varies from one pro environment to another. If you want to always use the same format, useSimpleDateFormat
with a specific format, as already suggested below. Also read http://www.sqlitetutorial.net/sqlite-date/– hkotsubo
Anyway, I updated it my answer with small test done on Sqlite (I don’t use it very often, so I may not have done it the best way, but the general idea doesn’t change much, so I believe it will help you) :-)
– hkotsubo