Long or Date in JSON

Asked

Viewed 3,800 times

3

Applications, one server and several customers, POST and GET to the server exchange objects JSON.

One of the variables that goes along with the JSON is the datahora atual, see different Strings to represent the dates, as:

"\"\\/Date(1335205592410)\\/\""         .NET JavaScriptSerializer
"\"\\/Date(1335205592410-0500)\\/\""    .NET DataContractJsonSerializer
"2012-04-23T18:25:43.511Z"              JavaScript built-in JSON object
"2012-04-21T18:25:43-05:00"             ISO 8601

Or even the error returned from the server, brings some examples of parsers for those dates:

(error: Failed to parse Date value 'Jun 7, 2017 08:44:51 AM': Can not parse date "Jun 7, 2017 11:44:51 AM": not compatible with any of standard forms ("yyyy-MM-dd'T'HH:mm:ss.SSSZ", "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", "EEE, dd MMM yyyy HH:mm:ss zzz", "yyyy-MM-dd"))

I researched, and little is said about representing that date in a long, sending and just creating a Date on the server or even saving as long in the bank, at least it seems simpler and would avoid these problems of reading the date.

I know it is possible to do this, but I was curious about the few implementations, or almost none that I found. This way, using the long, would be wrong, or else it would be some kind of "bad Practice"?

  • Dude, in my opinion it is not wrong to use long, because long is timeInMillis, IE, the current date in milliseconds, sending the information this way, you will not have these format divergences as in . net, javascript and etc..

2 answers

4


Depends on what you need.

The giant number, representing a long, is a timestamp. It represents a single instant, a point in the timeline, which corresponds to the amount of milliseconds since the Unix Epoch (at midnight on 01/01/1970 UTC).

The number 1335205592410, for example, represents the instant occurred 1335205592410ms after the Unix Epoch. The detail is that this same value corresponds to a different date and time, depending on the Timezone (time zone). Some examples:

  • 23 April 2012 at 15:26:32.410 in São Paulo
  • April 24, 2012 at 03:26:32.410 in Tokyo
  • 23 April 2012 at 18:26:32.410 in UTC

That is, the even timestamp value (1335205592410) corresponds to all the dates and times above. In São Paulo, this timestamp corresponds to the 23rd to the 3pm, and in Tokyo, to the 24th to the 3am. But the jiffy is the same, so the value of timestamp is the same for both.

That being said, timestamp is useful for when you need to know the exact moment something happened, regardless of time zones. If you need to "translate" the timestamp to a date and time, just use the Java Apis for this transformation.

Java >= 8

From Java 8 you can use the API java.time, much more modern and that corrects the several problems from the previous API (Date and Calendar).

To convert the timestamp to a date and time, we first use a java.time.Instant, that represents a specific instant (a point in the timeline), that is, exactly the same concept of a timestamp.

Then we convert this Instant for a java.time.ZonedDateTime, representing a date and time in a Timezone specific. Remember that the same timestamp corresponds to a different date and time in each Timezone, then we have to choose one and move on to the class java.time.ZoneId:

Instant instant = Instant.ofEpochMilli(1335205592410L);
// converte para o timezone America/Sao_Paulo
ZonedDateTime dataHoraSp = instant.atZone(ZoneId.of("America/Sao_Paulo"));
System.out.println(dataHoraSp);

The exit is:

2012-04-23T15:26:32.410-03:00[America/Sao_paulo]

The chosen Timezone was America/Sao_Paulo, which is in the format of IANA (always with this format Continente/Região). You can check all available timezones using the method ZoneId.getAvailableZoneIds(). And if you want to use Timezone default that is configured in JVM, just use ZoneId.systemDefault() instead of ZoneId.of(...).

The great advantage of using IANA timezones is that they have all the history of changes in the local time of each region. Therefore, adjustments such as daylight saving time are made automatically, and the ZonedDateTime will have the correct date and time values.

For more details on this API, I suggest oracle tutorial and this question. One detail is that if the bank you are using has a driver compatible with the JDBC 4.2, it is possible to work directly with the Instant, using the methods setObject class java.sql.PreparedStatement and getObject class java.sql.ResultSet:

PreparedStatement ps = ...
// seta o java.time.Instant
ps.setObject(1, datetime.toInstant());

// obter o Instant do banco
ResultSet rs = ...
Instant instant = rs.getObject(1, Instant.class);
// converter o instant para o timezone
ZonedDateTime zdt = instant.atZone(ZoneId.of("America/Sao_Paulo"));

Java <= 7

In Java <= 7, just convert the timestamp to java.util.Date. But since this class has no information about Timezone, the best way to get the date and time in a specific format is by using a java.text.SimpleDateFormat and setting a java.util.TimeZone in it:

// cria Date a partir do timestamp
Date date = new Date(1335205592410L);
// gerar String que corresponde à data e hora em um timezone
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
sdf.setTimeZone(TimeZone.getTimeZone("America/Sao_Paulo"));
System.out.println(sdf.format(date)); // 2012-04-23T15:26:32.410-03:00

The result will be a String date and time corresponding to the timestamp, in the Timezone specified in SimpleDateFormat:

2012-04-23T15:26:32.410-03:00

Another option is to create a java.util.Calendar, if you want to get the date and time fields individually, for example:

// ou crie um Calendar, se quiser obter os campos de data e hora individualmente
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("America/Sao_Paulo"));
calendar.setTimeInMillis(1335205592410L); // usar o valor do timestamp
System.out.println(calendar.get(Calendar.HOUR_OF_DAY)); // 15
System.out.println(calendar.get(Calendar.DAY_OF_MONTH)); // 23

Another alternative is to use the Threeten Backport, an excellent backport of java.time, which works on JDK 6 and 7. Classes have the same names and functionalities as java.time, the difference is that the package name is org.threeten.bp.


I must always use the timestamp?

It depends. What kind of information will you keep?

If you want to know exactly when something happened, regardless of the time zone, timestamp is the best option.

But what if you just want to record the date of birth for a simple record? That is, you only need the day, month and year (and you don’t need the hours or the time zone). In this case the timestamp is an exaggeration (because it has more information than you need), and in addition can cause unexpected errors.

Remember - again - that the same timestamp value can result in a different date and time depending on the chosen Timezone. If you simply create one java.util.Date, for example, and print, may have a different result for each run:

TimeZone.setDefault(TimeZone.getTimeZone("America/Sao_Paulo"));
// cria Date a partir do timestamp
Date date = new Date(1335205592410L);
System.out.println(date.getTime() + "=" + date);

// mudar o timezone default
TimeZone.setDefault(TimeZone.getTimeZone("Asia/Tokyo"));
System.out.println(date.getTime() + "=" + date);

Note that I printed the value of date.getTime() (which returns the timestamp value), and then the timestamp itself date, that internally will call your method toString(). The exit is:

1335205592410=Mon Apr 23 15:26:32 BRT 2012
1335205592410=Tue Apr 24 03:26:32 JST 2012

Note that although the timestamp has not changed, the date and time have changed. This is because Date, when printing, use Timezone default JVM to convert timestamp to a date and time. And when I changed Timezone default using TimeZone.setDefault, the return of toString() also changes. The value of timestamp does not change, but the value of the date and time it is printed yes. And this is also true for SimpleDateFormat, that when created, uses Timezone default that is currently set, then all dates being formatted (and all strings being parsed) will be affected as well (this can be avoided if you set Timezone, using setTimeZone in the SimpleDateFormat, as explained above).

The problem is that any application can call TimeZone.setDefault and affect all other applications that are running on the same JVM. And suddenly you start to see values of Date unexpected, although its value (the timestamp) has not changed.

Unfortunately, before Java 8 there was (using only the native API) no reliable way to save only one date (only day, month and year, no hours or Timezone). Even the class java.sql.Date, that was made to represent this concept, internally has a timestamp and suffers from the same problem (its value when printed changes according to Timezone default jvm).


In Java >= 8 it is possible to use the class java.time.LocalDate, that represents only one day, month and year, without any notion of schedules or timezones. In this case, there is no relationship with timestamps, because having only the date, without knowing the time and Timezone, there is no way to have the corresponding timestamp.

The advantage is that printed values are always the same, no matter what the Timezone default:

TimeZone.setDefault(TimeZone.getTimeZone("America/Sao_Paulo"));
// 23 de abril de 2012
LocalDate dt = LocalDate.of(2012, 4, 23);
System.out.println(dt);

// mudar o timezone default
TimeZone.setDefault(TimeZone.getTimeZone("Asia/Tokyo"));
System.out.println(dt);

Like LocalDate does not suffer interference from Timezone, the output is:

2012-04-23
2012-04-23

And in the same way that Instant, one LocalDate can also be used directly if the database already has a JDBC 4.2 compliant driver. See the respective documentation of your database to see if it is possible to use these classes.


About the given formats

First on these two:

/Date(1335205592410)/ 
/Date(1335205592410-0500)/

This is, in my opinion, one of the worst and most confusing formats ever created. Both have the value of timestamp, and the second still has a offset, which is the difference with UTC (-0500, which means "5 hours before UTC"). It should probably serve to convert the timestamp to a date and time at said offset.

Just as a curiosity, it is possible to do Parsing with the java.time:

import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.time.OffsetDateTime;


DateTimeFormatter parser = new DateTimeFormatterBuilder()
    // parte inicial
    .appendLiteral("/Date(")
    // para o timestamp, usa-se o InstantSeconds e os milissegundos
    .appendValue(ChronoField.INSTANT_SECONDS)
    .appendValue(ChronoField.MILLI_OF_SECOND, 3)
    // offset opcional (colchetes indicam que o campo é opcional)
    .appendPattern("[XX]")
    // se não tiver offset, assume-se que é zero (UTC)
    .parseDefaulting(ChronoField.OFFSET_SECONDS, 0)
    // parte final, e cria o DateTimeFormatter
    .appendLiteral(")/").toFormatter();
OffsetDateTime odt1 = OffsetDateTime.parse("/Date(1335205592410)/", parser);
System.out.println(odt1); // 2012-04-23T18:26:32.410Z
OffsetDateTime odt2 = OffsetDateTime.parse("/Date(1335205592410-0500)/", parser);
System.out.println(odt2); // 2012-04-23T13:26:32.410-05:00

I chose to java.time.OffsetDateTime, as it represents a date and time at a specific offset (which in the first case is zero, which is the same as saying that the date/time is in UTC, and in the second case is -0500, or "5 hours before UTC"). But both dates obtained correspond to the same instant (the same timestamp).

Already these other formats:

2012-04-23T18:25:43.511Z
2012-04-21T18:25:43-05:00

Both are in format ISO 8601. The first is in UTC (is what the "Z" at the end means), and the second is on offset -05:00 (5 hours before UTC). Note that offset can be written with or without colon (-05:00 or -0500), or still have only the value of hours (-05). But I recommend always write the minutes too, since there are countries that use half-hour time zones, and even 45 minutes.

Another advantage of java.time is that it already supports ISO 8601 format by default:

OffsetDateTime d1 = OffsetDateTime.parse("2012-04-23T18:25:43.511Z");
System.out.println(d1);
OffsetDateTime d2 = OffsetDateTime.parse("2012-04-21T18:25:43-05:00");
System.out.println(d2);

This code prints out:

2012-04-23T18:25:43.511Z
2012-04-21T18:25:43-05:00

The advantage of ISO 8601 is to be more intelligible to humans (since when using timestamp, it is not so obvious to know which date and time it refers to), and we can have "partial" values, such as 2012-04-23 (only the date, without the hours and offset), 18:25 (hours only), etc.

The difference is that these "partial" values cannot be translated into a timestamp. For example, if I only have the date "April 23, 2012". Without a time and a Timezone, there is no way to know the exact moment that this date represents, and so you cannot get a single timestamp value (some Apis even do this conversion, but they implicitly choose some arbitrary value for the time and Timezone, and some don’t even allow you to configure it). But if you just want to register your users' date of birth, without worrying about the time and time zone, then just having the day, month and year is enough.

If you need to transmit dates and times as strings (only these values, regardless of Timezone), the ISO 8601 format is an excellent option.

Just don’t confuse strings with dates. A date/time represents a concept: the idea of a calendar point, an instant in the timeline, of a specific moment of the day. A string is just a way to represent this concept in text form. For example, the date "March 1, 2000" represents this specific point of the calendar, but it can be written in several ways:

  • 01/03/2000
  • March 3th, 2000
  • 2000-03-01
  • etc....

These strings are different, but all represent the same date. "Translating" to Java, it’s like I’ve got:

String s1 = "01/03/2000";
String s2 = "March 3th, 2000";
String s3 = "2000-03-01";
LocalDate data = LocalDate.of(2000, 3, 1);

Each String has a different value, but all represent the same date (the same value as the LocalDate). That is, the same numerical values of the day, month and year that are contained in the class LocalDate. A date has only the values, and when we need to see them (printing on the screen, for example) or send them (either in a file, or in the response of a JSON service), we need to transform them to a string in some format.


Finally, use timestamp (the value of long) it’s not right or wrong, it all depends on what you need.

0

Some databases have the type datetime With TimeZone, And with this it does the management of Timezone, if you save in milliseconds it will not be possible for example to know the time zone and not even if you are in summer time or not for example. If this doesn’t make a difference to you, save in long, but the recommended is to use the Date, and to solve this problem you must make a convert before sending to the server.

Browser other questions tagged

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