First we have to understand what really class java.util.Date
means. Despite the name, it does not represent a date. At least not in the sense of representing a single day, month, year, hour, minute and second value.
In fact a Date
represents a timestamp. The only value she possesses is a long
: a number representing the amount of milliseconds since the Unix Epoch (The Unix Epoch is January 1, 1970, at midnight on UTC). The timestamp value can be obtained by the method getTime()
.
What you can confuse by using Date
is that by printing it, the Timezone default of JVM is used to translate the timestamp to a date and time.
A classic test is to print the Date
several times, changing the Timezone default:
Date d = new Date();
TimeZone.setDefault(TimeZone.getTimeZone("America/Sao_Paulo"));
System.out.println(d.getTime() + "=" + d);
TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles"));
System.out.println(d.getTime() + "=" + d);
TimeZone.setDefault(TimeZone.getTimeZone("Asia/Tokyo"));
System.out.println(d.getTime() + "=" + d);
The exit is:
1554052464000=Sun Mar 31 14:14:24 BRT 2019
1554052464000=Sun Mar 31 10:14:24 PDT 2019
1554052464000=Mon Apr 01 02:14:24 JST 2019
Note that the timestamp value (returned by getTime()
) does not change, ie the Date
is always the same. But when printing it, it uses Timezone default to get the date and time values. See that in São Paulo the day is 03/31/2019 and the time is 14:14:24. But in Los Angeles the time is 10:14, and in Japan it’s already 01/04 at 2:14 in the morning.
This is the idea of timestamp: its value is the same all over the world (no matter where the computer is or which Timezone is configured, they would all get 1554052464000 as the current timestamp if they ran this code at the same time as me). What changes are the corresponding date and time values, as these vary from one time zone to another.
Therefore, what a java.util.Date
represents is the value of timestamp. Any value derived from it (day, month, year, hour, minute, second) depends on Timezone default of the JVM. But these values nay are part of the Date
. When you use the getters and setters, he checks the Timezone default of the JVM and the timestamp, and checks the respective value of the field under these conditions.
That being said, from the exit you reported, it looks like you’re actually using a java.sql.Timestamp
. And as this is a subclass of java.util.Date
, it has the same characteristics: all it has is the timestamp value, and if you print it, the date and time values also change with Timezone default jvm.
Anyway, to manipulate the fields, you can use a java.util.Calendar
:
Date d = ...
Timestamp t = ...
// criar o Calendar
Calendar cal = Calendar.getInstance();
// setar o timestamp
cal.setTimeInMillis(t.getTime());
// se for usar o timestamp de Date, faça
// cal.setTimeInMillis(d.getTime()); ou simplesmente cal.setTime(d);
// mudar o horário para 23:59:59.999
cal.set(Calendar.HOUR_OF_DAY, 23);
cal.set(Calendar.MINUTE, 59);
cal.set(Calendar.SECOND, 59);
cal.set(Calendar.MILLISECOND, 999);
// criar novas instâncias com o horário atualizado
t = new Timestamp(cal.getTimeInMillis());
d = cal.getTime();
Always remembering that Calendar
will also work with Timezone default of the JVM. If you want the date and time in a different Timezone, you must pass it in the method getInstance()
:
// usar timezone do Japão em vez do default da JVM
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("Asia/Tokyo"));
The Timezone used will generate a different timestamp value, as 23:59 occurs at a different instant in each part of the world. Most of the time people just use the default without thinking because all the servers are configured with the same Timezone and everything "works". But it is important to keep in mind that the chosen Timezone can influence the final values.
Another detail is that dates have no format (As stated above here, here and here.)
The exit you see (31/03/2019 13:23:14.958
) is just a form that classes have chosen to represent their values. In the case of java.sql.Timestamp
, by default it converts timestamp to Timezone default of the JVM, gets its date and time values and displays them in this format. But that doesn’t mean that the date is in that format.
Java >= 8
From Java 8 there is a API java.time
, much better than the legacy classes (Date
, Calendar
, etc.).
In this API, you can choose the most suitable type to work with your dates. In case, you can for example map the java.sql.Timestamp
for a java.time.LocalDateTime
:
Timestamp t = ...
LocalDateTime ldt = t
// converte para LocalDateTime (usa o timezone default da JVM)
.toLocalDateTime()
// seta o horário para 23:59:59.999999999
.with(LocalTime.MAX);
// converte de volta para Timestamp
t = Timestamp.valueOf(ldt);
I used java.time.LocalTime
to set the time, and the constant MAX
which corresponds to 23:59:59.999999999.
The conversions from/to LocalDateTime
use the Timezone default from the JVM to know the date and time values corresponding to the timestamp. But if you want to use a specific Timezone, you can convert the classes to java.time.Instant
(the class representing the concept of timestamp), and then use a java.time.ZoneId
(the class representing a Timezone).
Date d = ...
ZoneId zone = ZoneId.of("America/Sao_Paulo"); // timezone que quero usar na conversão
ZonedDateTime zdt = d.toInstant() // obtém o Instant (representa o timestamp)
// converte para um timezone
.atZone(zone)
// obtém a data e o início do dia seguinte
.toLocalDate().plusDays(1).atStartOfDay(zone)
// subtrai 1 nanossegundo (para obter o último instante do dia anterior)
.minusNanos(1);
// converte de volta para Date
d = Date.from(zdt.toInstant());
// este código também funciona para java.sql.Timestamp
// pois esta classe também possui os métodos toInstant() e from(Instant)
The result is a java.time.ZonedDateTime
. The difference is that this class considers Timezone, and LocalDateTime
nay.
You’re probably wondering why this whole complication of getting the start of the next day and subtracting 1 nanosecond. This is because timezones have daylight savings time and if I simply set the time to 23:59:59.999, the result will not always be the last instant of that day. In the previous example I could do this because LocalDateTime
does not have information about the Timezone and therefore does not suffer interference from daylight saving time.
For example, when summer time ends in Brazil, at midnight the clock is set 1 hour, back to 11 pm. That is, minutes between 23:00 and 23:59 occur twice, one in daylight time and the other in normal time. If I set the time to 23:59 manually, which of these occurrences will be set, that of daylight saving or normal time? To avoid these problems, the above method is more guaranteed as I take the last instant of the day, on the Timezone in question, regardless of whether I have daylight saving time or not.
If the database you are using has a driver compatible with the JDBC 4.2, it is possible to work directly with the classes of the java.time
, using the methods setObject
class java.sql.PreparedStatement
and getObject
class java.sql.ResultSet
. An example with LocalDateTime
would be:
LocalDateTime dt = ...
PreparedStatement ps = ...
// seta o java.time.LocalDateTime
ps.setObject(1, dt);
// obter o LocalDateTime do banco
ResultSet rs = ...
LocalDateTime dt = rs.getObject(1, LocalDateTime.class);
...
Just remembering that not all databases support all types of java.time
. See the documentation and see which classes are mapped to which types in the database.
Okay hkotsubo, thank you for the great explanation, I got a better understanding of how it works. I will make the changes here in the code. I believe it will work. Hugs.
– Mateus