How to handle different date formats?

Asked

Viewed 1,314 times

3

I have an application that at a certain time I recover the obtained date from the system, save it in a table in the database (Sqlite) and then recover it.

The problem is that in determinator devices have the default date dd/MM/yyyy (01/01/1970) and others dd 'de' MMM 'de' yyyy (01 de Jan de 1970).

As a solution I see the date output to then use one of these lines:

SimpleDateFormat motorola = new SimpleDateFormat("dd 'de' MMM 'de' yyyy"); // compila para MOTOROLA
SimpleDateFormat samsung = new SimpleDateFormat("dd/MM/yyyy"); //compila para Samsung

Is there a way to use regex to simplify things? It would be great to obtain the universality of the code, regardless of the device used.

It would be possible to compare this date received directly on SimpleDateFormat?

String DatePattern = "^(?:(31)(\D)(0?[13578]|1[02])\2|(29|30)(\D)(0?[13-9]|1[0-2])\5|(0?[1-9]|1\d|2[0-8])(\D)(0?[1-9]|1[0-2])\8)((?:1[6-9]|[2-9]\d)?\d{2})$|^(29)(\D)(0?2)\12((?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:16|[2468][048]|[3579][26])00)$";

How would I get a pattern for those dates?

For example, the output will always be dd/MM/yyyy.

  • 1

    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?

  • 1

    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

  • 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);

  • 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, use SimpleDateFormat with a specific format, as already suggested below. Also read http://www.sqlitetutorial.net/sqlite-date/

  • 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) :-)

3 answers

5

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));
  • 1

    Seeing your issue, it seems that the my answer was useful to you :)

  • 1

    @ramaral Now I realized I forgot to vote on your reply! Thank you :-)

  • 1

    To make it clearer: to use the java.time it is necessary minSdkVersion >= 26, it’s not enough to have compileSdkVersion >= 26, that is, a program that uses java.time only runs on Android devices 8 or higher

  • @ramaral That detail I did not know. I updated the reply, thanks again! :-)

  • I see that if my code gets depreciated, I will be able to solve using the tips.

2


To get a string in the desired format, regardless of the device/user settings, do so:

//Obtenha um Calendar com a data actual
Calendar calendar = Calendar.getInstance();

// manipule a data como pretender,
// por exemplo, adicionar 5 dias
calendar.add(Calendar.DAY_OF_MONTH, 5)

//Construa um DateFormat, com o o formato pretendido
SimpleDateFormat df = new SimpleDateFormat("dd/MM/yyyy");

// Obtenha a String que representa a data nesse formato
String formattedDate = df.format(calendar.getTime());

0

I don’t know if it’s feasible for your project to change the date data type but I’ll explain how I work with dates and I don’t have these problems that you’re having.

Always when working with date I prefer to use epoch timestamp mainly for two reasons:

  • When I go to save in the bank it is only a whole, from this moment it is 1556271003, it facilitates in the interoperability between different providers of databases

  • If I want to add X time on a date just add the seconds, or milliseconds, in the integer representing the date

Then in your case I would do the following, saved and works always with the date being a timestamp and when it will show to the user you format as you want the output. Example:

long unixSeconds = System.currentTimeMillis();
Date date = new java.util.Date(unixSeconds); 
// o formato que você quer a saida
SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); 
// seta o timezone
sdf.setTimeZone(java.util.TimeZone.getTimeZone("GMT-4")); 
String formattedDate = sdf.format(date);
System.out.println(formattedDate);

Code adapted from: https://stackoverflow.com/a/17433005/6009128

  • 1

    Adding X time is not always so trivial with java.util.Date, because there are several "pranks" if you are going to add days, and mainly months and years - in such cases, it is best to use java.util.Calendar (or the API java.time, if available) :-)

  • yes, there are some things that have to be taken care of even, but if I always work with the epoch and go adding or decreasing the seconds have no problems, correct?

  • 1

    It depends on what you want to do. If you’re adding up hours, minutes or seconds, it’s not much of a problem. If adding up days, months or years, the situations mentioned in the links I went through, then just adding up to the value of timestamp is not enough

  • in my case I would like to get the same date pattern independent of the device. would I have to edit the date with "new Locale("en", "BR")" before writing to the database? Only the java.time.format API.Datetimeformatter can do this? . ofPattern("[dd/MM/Uuuu][dd 'de' MMM 'de' Uuuu]" ?

  • The problem is in only receive a standardized date, just to complement the understanding, if so facilitates: [Loan]get a dateAtual and we are 5 days to a dateDevolution, after that, [Renewal] I receive dateDevolution and add 1 more day getting Date Renewal.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.