Reputation: 1683
This started as a simple error: I had YYYY
instead of yyyy
in my format string for a SimpleDateFormat
object. But I'm totally baffled by the results of my tests with the incorrect format string.
This code:
@Test
public void whatTheHell() {
try {
SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/YYYY");
Date d1 = sdf.parse("01/07/2016");
Date d2 = sdf.parse("02/08/2016");
Date d3 = sdf.parse("11/29/2027");
System.out.println(d1.toString());
System.out.println(d2.toString());
System.out.println(d3.toString());
} catch (ParseException pe) {
fail("ParseException: " + pe.getMessage());
}
}
produces this output:
Sun Dec 27 00:00:00 PST 2015
Sun Dec 27 00:00:00 PST 2015
Sun Dec 27 00:00:00 PST 2026
I've read the documentation on the 'Y' parameter here: https://docs.oracle.com/javase/7/docs/api/java/util/GregorianCalendar.html, but I still can't see the logic that's working here. Particularly the last instance: I can kinda-sorta understand how the dates in January (& maybe February) can be translated into December of the previous year, but moving the date of November 29th backwards by 11 months baffles me. And what's so special about December 27th?
Can anyone explain?
MORE INFORMATION
@Jan suggested that relying on the toString() method could be a problem, so I defined a date format to print YYYY MM dd '-' yyyy MM dd
in the same code as above. Here is the additional output:
2016 12 27 - 2015 12 27
2016 12 27 - 2015 12 27
2027 12 27 - 2026 12 27
Upvotes: 39
Views: 31307
Reputation: 340098
A week can be defined in different ways. For example, in the United States, the first day of the week is considered to be Sunday most often while in Europe and many other places the first day is Monday.
Likewise, the week of a week-based year can also be defined in different ways.
The legacy classes you are using implicitly use the definitions specified by a Locale
. The locale being applied is also implicit, using the JVM’s current default Locale
if you do not otherwise specify.
For even more interesting details about the difficulty in defining a week, see this Question, Different behavior of WeekFields on JVM 8 and JVM 10
There is a practical international standard for date-time handling, ISO 8601.
The ISO 8601 definition of a week is:
I suggest using the ISO 8601 standard definition whenever possible. This standard definition is simple and logical, with increasing adoption across industries.
The java.time classes offer some support for week of week-based year in the WeekFields
class.
LocalDate ld = LocalDate.of( 2019 , Month.JANUARY , 1 ) ;
long week = ld.get( WeekFields.ISO.weekOfWeekBasedYear() ) ;
See this code run live at IdeOne.com.
ld.toString(): 2019-01-01
week: 1
org.threeten.extra.YearWeek
But if doing much of this work, I suggest adding the ThreeTen-Extra library to your project. You will find the YearWeek
class to be helpful. This class offers several handy methods such as generating a LocalDate
for any day within that week.
LocalDate ld = LocalDate.of ( 2019 , Month.JANUARY , 1 );
YearWeek yw = YearWeek.from ( ld );
LocalDate startOfWeek = yw.atDay ( DayOfWeek.MONDAY );
ld.toString(): 2019-01-01
yw.toString(): 2019-W01
startOfWeek.toString(): 2018-12-31
Notice how the first day of the year in the week-based year of 2019 is a date from the previous calendar year, 2018 rather than 2019.
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date
, Calendar
, & SimpleDateFormat
.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.*
classes.
Where to obtain the java.time classes?
Upvotes: 13
Reputation: 21640
It's simple: December 27 2015 is day 1 of week 1 of week-year 2016 (and December 27 2026 is day 1 of week 1 of week-year 2027). This can be verified by adding these lines:
SimpleDateFormat odf = new SimpleDateFormat("YYYY-ww-u");
System.out.println(odf.format(d1));
System.out.println(odf.format(d2));
System.out.println(odf.format(d3));
If a SimpleDateFormat
outputs a date it can use all fields: year, month, day, day of week, week of month, week in year, week-year etc.
On parsing, SimpleDateFormat
expects a matching set of values: either day, month, year or day of week, week in year, week-year. Since you supplied a week-year but did not supply day of week and week in year, those to values have been assumed as 1.
The actual values depend on your locale:
(see https://docs.oracle.com/javase/7/docs/api/java/util/GregorianCalendar.html#week_and_year)
On my system (using de-ch locale, with "EEE MMM dd HH:mm:ss zzz yyyy - YYYY-ww-u" as format) I get
Mo Jan 04 00:00:00 MEZ 2016 - 2016-01-1 Mo Jan 04 00:00:00 MEZ 2016 - 2016-01-1 Mo Jan 04 00:00:00 MEZ 2027 - 2027-01-1
Upvotes: 16