-1

I'm using java.util.Date. The expression:

new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2016-10-16 00:00:00").toString()

is returning "Sun Oct 16 01:00:00 BRST 2016" (a wrong date) for this particular date, but a correct response on most other dates.

I also tried a format string taken from Oracle docs: "yyyy-MM-dd'T'HH:mm:ss.SSSZ" and a specific date: "2016-10-16T00:00:00.000-0300" and got the same "error" (I suppose), one hour ahead:

Sun Oct 16 01:00:00 BRST 2016

Leao
  • 11
  • 3
  • I also tried a format string taken from Oracle docs: "yyyy-MM-dd'T'HH:mm:ss.SSSZ" and a specific date: "2016-10-16T00:00:00.000-0300" and got the same "error" (I suppose): Sun Oct 16 01:00:00 BRST 2016, one hour ahead. – Leao Oct 09 '17 at 12:58
  • 3
    That code won't return `"2016-10-16 01:00:00"` because `Date.toString()` never returns a string in that format. A [mcve] would have been useful - including the time zone your system is in. I strongly suspect this is just a matter of a time zone transition due to daylight saving time. – Jon Skeet Oct 09 '17 at 13:04
  • @Leao I've added you comment's code in the question. You can [edit] your question at anytime to add more information. It's much better and more readable (specially for code) than putting it in the comments. –  Oct 09 '17 at 13:56
  • 1
    @JonSkeet Indeed, it's DST in Brazil. I inferred that from the "BRST" in the output (that's the short name for Brazil's summer time) **and** the date/time when the change happens. –  Oct 09 '17 at 14:32

2 Answers2

2

That usually happens due to Daylight Saving Time (DST) (also known as "summer time"). Based on the date/time, and on the output you've got (Sun Oct 16 01:00:00 BRST 2016), I suppose it's Brazil's DST (BRST is the abbreviation for Brazilian's summer time).

SimpleDateFormat uses the JVM's default timezone (if you don't specify one), so probably your default zone is America/Sao_Paulo or Brazil/East (you can check that by calling TimeZone.getDefault().getID()).

In America/Sao_Paulo timezone, DST started at October 16th 2016: at midnight, clocks shifted 1 hour forward from midnight to 1 AM (and the offset changed from -03:00 to -02:00). So all local times between 00:00 and 00:59 didn't exist in this timezone (you can also think that clocks changed from 23:59:59.999999999 directly to 01:00).

That's why this specific date in midnight (which didn't exist in this timezone) is automatically shifted to the next valid moment (1 AM). But if I set a specific timezone in the formatter, this won't happen:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// set formatter to use UTC (instead of JVM default timezone)
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
// parse it as midnight (no shift to 01:00)
Date date = sdf.parse("2016-10-16 00:00:00");

In this case, I'm using UTC, which has no DST effects. But remind that the date created above will be equivalent to midnight in UTC (which is the same instant as October 15th 2016 at 9 PM in Brazil (the day before) - maybe it's not what you want).

Be aware of that before changing the timezone: if you want a specific instant (a precise point in time), changing the timezone will affect the final result. If you just want to consider the date/time values and don't care about in what timezone it is (treating the value as it's a "local date/time"), just set the formatter to use UTC, to avoid DST effects (an ugly workaround, IMO, but just because the java.util.Date API doesn't have specific types for local date/times).

But anyway, this is not an error. It's the expected behaviour (DST and timezones have lots of strange and non-intuitive behaviours, but that's the way it is).


Java new Date/Time API

The old classes (Date, Calendar and SimpleDateFormat) have lots of problems and design issues, and they're being replaced by the new APIs.

If you're using Java 8, consider using the new java.time API. It's easier, less bugged and less error-prone than the old APIs.

If you're using Java 6 or 7, you can use the ThreeTen Backport, a great backport for Java 8's new date/time classes. And for Android, you'll also need the ThreeTenABP (more on how to use it here).

The code below works for both. The only difference is the package names (in Java 8 is java.time and in ThreeTen Backport (or Android's ThreeTenABP) is org.threeten.bp), but the classes and methods names are the same.

This new API has lots of new types that best suit different use-cases. In your case, if you want just the date and time fields and don't care about timezones, you can use a LocalDateTime. To parse it, just use a DateTimeFormatter:

DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime dt = LocalDateTime.parse("2016-10-16 00:00:00", fmt);
System.out.println(dt); // 2016-10-16T00:00

This will ignore DST effects, because a LocalDateTime has no timezone information.

But if you want to consider the timezone, you can convert this to a ZonedDateTime, using a ZoneId to get the timezone:

// convert to a timezone
ZonedDateTime z = dt.atZone(ZoneId.of("America/Sao_Paulo"));
System.out.println(z); // 2016-10-16T01:00-02:00[America/Sao_Paulo]

In this case, note that the time was adjusted to 1 AM, because I converted to America/Sao_Paulo timezone, so the DST effects were considered, as already explained above.


With this new API we can look closer at what's happening in this specific timezone, for this particular date/time. First I'll create a ZonedDateTime that corresponds to October 15th 2016, at 23:59:59 in America/Sao_Paulo timezone, and then I'll add 1 second to it:

// October 15th 2016, at 23:59:59 in Sao Paulo timezone
ZonedDateTime z = ZonedDateTime.of(2016, 10, 15, 23, 59, 59, 0, ZoneId.of("America/Sao_Paulo"));
System.out.println(z); // 2016-10-15T23:59:59-03:00[America/Sao_Paulo]
System.out.println(z.plusSeconds(1)); // 2016-10-16T01:00-02:00[America/Sao_Paulo]

Note that the original date is in offset -03:00 (3 hours behind UTC, which is the standard offset for America/Sao_Paulo timezone). One second later, it should be midnight, but due to DST change, the clock shifts directly to 1 AM, and the offset changes to -02:00.

Even if I try to directly create October 16th 2016 at midnight in this timezone, the value will be corrected, because this local time doesn't exist in this timezone, due to DST shift:

// Try to create October 16th 2016, at midnight in Sao Paulo timezone
ZonedDateTime z = ZonedDateTime.of(2016, 10, 16, 0, 0, 0, 0, ZoneId.of("America/Sao_Paulo"));
System.out.println(z); // 2016-10-16T01:00-02:00[America/Sao_Paulo]

So, it's not an error. October 16th 2016 at midnight in America/Sao_Paulo timezone doesn't exist due to a DST change, and the API's automatically corrects this to the next valid moment (which is, in this case, 1 AM).


The API uses IANA timezones names (always in the format Region/City, like America/Sao_Paulo or Europe/Berlin). Avoid using the 3-letter abbreviations (like CST or PST) because they are ambiguous and not standard.

You can get a list of available timezones (and choose the one that fits best your system) by calling ZoneId.getAvailableZoneIds().

You can also use the system's default timezone with ZoneId.systemDefault(), but this can be changed without notice, even at runtime, so it's better to explicity use a specific one.

  • You're absolutely right Hugo. DST started october 16th last year and october 15th this year. My fault. Thank you. – Leao Oct 10 '17 at 02:28
  • @Leao You're welcome, glad to help! If you found the answer helpful and it solved your problem (aka "it answers to your question"), you can accept it (see how to do it [here](https://stackoverflow.com/help/someone-answers) and [here](https://stackoverflow.com/help/accepted-answer)). You're not obliged to do it (only if you found the answer helpful and solved your problem), but it's a good practice to indicate future visitors that the answer is helpful and answers to the question. –  Oct 10 '17 at 13:08
-1

Your problem is probably the timezone, so you could use:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));

Here is the original answer.

NikNik
  • 2,191
  • 2
  • 15
  • 34