Y 以 SimpleDateFormat 返回2012,而 y 以 SimpleDateFormat 返回2011

我想知道为什么在 SimpleDateFormat中,“ Y”返回2012年,而“ y”返回2011年:

System.out.println(new SimpleDateFormat("Y").format(new Date())); // prints 2012
System.out.println(new SimpleDateFormat("y").format(new Date())); // prints 2011

有人能解释一下原因吗?

33979 次浏览

week year and year. From javadoc

A week year is in sync with a WEEK_OF_YEAR cycle. All weeks between the first and last weeks (inclusive) have the same week year value. Therefore, the first and last days of a week year may have different calendar year values.

For example, January 1, 1998 is a Thursday. If getFirstDayOfWeek() is MONDAY and getMinimalDaysInFirstWeek() is 4 (ISO 8601 standard compatible setting), then week 1 of 1998 starts on December 29, 1997, and ends on January 4, 1998. The week year is 1998 for the last three days of calendar year 1997. If, however, getFirstDayOfWeek() is SUNDAY, then week 1 of 1998 starts on January 4, 1998, and ends on January 10, 1998; the first three days of 1998 then are part of week 53 of 1997 and their week year is 1997.

Format Y to get week year if calendar support week year. (getCalendar().isWeekDateSupported())

I learned the hard way the JSTL tag library format:date with short as the requested format uses YYYY under the covers. Which can indeed roll the printed date ahead a year.

Here's a Java 8 update with some code, as GregorianCalendar will probably be deprecated or removed from future JDK versions.

The new code is handled in the WeekFields class, and specifically for the lower case y / upper case Y with the weekBasedYear() field accessor.

Returns a field to access the year of a week-based-year based on this WeekFields. This represents the concept of the year where weeks start on a fixed day-of-week, such as Monday and each week belongs to exactly one year. This field is typically used with dayOfWeek() and weekOfWeekBasedYear().

Week one(1) is the week starting on the getFirstDayOfWeek() where there are at least getMinimalDaysInFirstWeek() days in the year. Thus, week one may start before the start of the year. If the first week starts after the start of the year then the period before is in the last week of the previous year.

This field can be used with any calendar system.

In the resolving phase of parsing, a date can be created from a week-based-year, week-of-year and day-of-week.

In strict mode, all three fields are validated against their range of valid values. The week-of-year field is validated to ensure that the resulting week-based-year is the week-based-year requested.

In smart mode, all three fields are validated against their range of valid values. The week-of-week-based-year field is validated from 1 to 53, meaning that the resulting date can be in the following week-based-year to that specified.

In lenient mode, the year and day-of-week are validated against the range of valid values. The resulting date is calculated equivalent to the following three stage approach. First, create a date on the first day of the first week in the requested week-based-year. Then take the week-of-week-based-year, subtract one, and add the amount in weeks to the date. Finally, adjust to the correct day-of-week within the localized week.

The setup of this WeekFields instance depends on the locale and may have different settings depending on it, US and European countries like France may have a different day as start of the week.

For example the DateFormatterBuilder of Java 8, instantiate the parser with the locale, and use this locale for the Y symbol :

public final class DateTimeFormatterBuilder {
...


private void parsePattern(String pattern) {
...
} else if (cur == 'Y') {
// Fields defined by Locale
appendInternal(new WeekBasedFieldPrinterParser(cur, count));
} else {
...




static final class WeekBasedFieldPrinterParser implements DateTimePrinterParser {
...


/**
* Gets the printerParser to use based on the field and the locale.
*
* @param locale  the locale to use, not null
* @return the formatter, not null
* @throws IllegalArgumentException if the formatter cannot be found
*/
private DateTimePrinterParser printerParser(Locale locale) {
WeekFields weekDef = WeekFields.of(locale);
TemporalField field = null;
switch (chr) {
case 'Y':
field = weekDef.weekBasedYear();
if (count == 2) {
return new ReducedPrinterParser(field, 2, 2, 0, ReducedPrinterParser.BASE_DATE, 0);
} else {
return new NumberPrinterParser(field, count, 19,
(count < 4) ? SignStyle.NORMAL : SignStyle.EXCEEDS_PAD, -1);
}
case 'e':
case 'c':
field = weekDef.dayOfWeek();
break;
case 'w':
field = weekDef.weekOfWeekBasedYear();
break;
case 'W':
field = weekDef.weekOfMonth();
break;
default:
throw new IllegalStateException("unreachable");
}
return new NumberPrinterParser(field, (count == 2 ? 2 : 1), 2, SignStyle.NOT_NEGATIVE);
}


...
}


...
}

Here's some example

System.out.format("Conundrum                         : %s%n",
ZonedDateTime.of(2015, 12, 30, 0, 0, 0, 0, ZoneId.of("UTC"))
.format(DateTimeFormatter.ofPattern("YYYYMMdd'T'HHmms'S'")));
System.out.format("Solution                          : %s%n",
ZonedDateTime.of(2015, 12, 30, 0, 0, 0, 0, ZoneId.of("UTC"))
.format(DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmms'S'")));




System.out.format("JVM Locale first day of week      : %s%n",
WeekFields.of(Locale.getDefault()).getFirstDayOfWeek());
System.out.format("US first day of week              : %s%n",
WeekFields.of(Locale.US).getFirstDayOfWeek());
System.out.format("France first day of week          : %s%n",
WeekFields.of(Locale.FRANCE).getFirstDayOfWeek());
System.out.format("JVM Locale min days in 1st week   : %s%n",
WeekFields.of(Locale.getDefault()).getMinimalDaysInFirstWeek());
System.out.format("US min days in 1st week           : %s%n",
WeekFields.of(Locale.US).getMinimalDaysInFirstWeek());
System.out.format("JVM Locale min days in 1st week   : %s%n",
WeekFields.of(Locale.FRANCE).getMinimalDaysInFirstWeek());


System.out.format("JVM Locale week based year (big Y): %s%n",
ZonedDateTime.of(2015, 12, 30, 0, 0, 0, 0, ZoneId.of("UTC")).get(WeekFields.of(Locale.FRANCE).weekBasedYear()));
System.out.format("France week based year (big Y)    : %s%n",
ZonedDateTime.of(2015, 12, 30, 0, 0, 0, 0, ZoneId.of("UTC")).get(WeekFields.of(Locale.FRANCE).weekBasedYear()));
System.out.format("US week based year (big Y)        : %s%n",
ZonedDateTime.of(2015, 12, 30, 0, 0, 0, 0, ZoneId.of("UTC")).get(WeekFields.of(Locale.US).weekBasedYear()));

And in regard of the locale and the upper case Y, you can either play with the command line option -Duser.language= (fr, en, es, etc.), or force the locale at invocation time :

System.out.format("English localized                 : %s%n",
ZonedDateTime.of(2015, 12, 30, 0, 0, 0, 0, ZoneId.of("UTC"))
.format(DateTimeFormatter.ofPattern("YYYYMMdd'T'HHmms'S'", Locale.ENGLISH)));
System.out.format("French localized                  : %s%n",
ZonedDateTime.of(2015, 12, 30, 0, 0, 0, 0, ZoneId.of("UTC"))
.format(DateTimeFormatter.ofPattern("YYYYMMdd'T'HHmms'S'", Locale.FRENCH)));

I convert a date back and forth - you would expect the same year when you do this.

Notice how it advances one!

This is bad: YYYY! YYYY

You can run it here.

import java.util.Date;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import static java.lang.System.out;
class Playground {
public static Date convertYYYYMMDDStr(String s) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date result = null;
try {
result = sdf.parse(s);
} catch(ParseException e) {
e.printStackTrace();
}
return result;
}
public static String formatDateToStrWithSDF(Date d, SimpleDateFormat s) {
return s.format(d);
}
public static void main(String[ ] args) {
// DON'T DO. Use yyyy instead of YYYY
SimpleDateFormat sdfdmy = new SimpleDateFormat("dd-MM-YYYY");
String jan1st2020sb = "2020-01-01";
Date jan1st2020d = convertYYYYMMDDStr(jan1st2020sb);
String jan1st2020sa = formatDateToStrWithSDF(jan1st2020d, sdfdmy);
out.println(jan1st2020sb);
out.println(jan1st2020d);
out.println(jan1st2020sa);
String dec31st2020sb = "2020-12-31";
Date dec31st2020d = convertYYYYMMDDStr(dec31st2020sb);
String dec31st2020sa = formatDateToStrWithSDF(dec31st2020d, sdfdmy);
out.println(dec31st2020sb);
out.println(dec31st2020d);
out.println(dec31st2020sa);
}
}

This is good: yyyy

yyyy