Difference Between Year-Of-Era and Week-Based-Year

What is the difference between year and year-of-era?

The answer lies in the documentation for IsoChronology

  • era - There are two eras, 'Current Era' (CE) and 'Before Current Era' (BCE).
  • year-of-era - The year-of-era is the same as the proleptic-year for the current CE era. For the BCE era before the ISO epoch the year increases from 1 upwards as time goes backwards.
  • proleptic-year - The proleptic year is the same as the year-of-era for the current era. For the previous era, years have zero, then negative values.

u will give you the proleptic year.
y will give you the year of the era.

The difference is mainly important for years of the BC era. The proleptic year 0 is actually 1 BC, it is followed by proleptic year 1 which is 1 AD. The proleptic year can be negative, the year of era can not.

Here is a snippet that will help visualize how it works :

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("'proleptic' : u '= era:' y G");

for (int i = 5; i > -6 ; i--) {
LocalDate localDate = LocalDate.of(i, 3, 14);
System.out.println(formatter.format(localDate));
}

Output:

proleptic : 5 = era: 5 AD
proleptic : 4 = era: 4 AD
proleptic : 3 = era: 3 AD
proleptic : 2 = era: 2 AD
proleptic : 1 = era: 1 AD
proleptic : 0 = era: 1 BC
proleptic : -1 = era: 2 BC
proleptic : -2 = era: 3 BC
proleptic : -3 = era: 4 BC
proleptic : -4 = era: 5 BC
proleptic : -5 = era: 6 BC

DateTimeFormatter week-based-year diff

The week-based-year field, according to javadoc, depends on 2 things: what's the first day of the week, and the minimum number of days in the first week.

The ISO standard defines Monday as the first day of the week, and a minimum of 4 days in the first week:

System.out.println(WeekFields.ISO.getFirstDayOfWeek()); // Monday
System.out.println(WeekFields.ISO.getMinimalDaysInFirstWeek()); // 4

(WeekFields.ISO.weekBasedYear() is equivalent to IsoFields.WEEK_BASED_YEAR, with minor differences regarding another calendar systems)

Considering, for example, January 2nd 2009, which was a Friday. Checking the javadoc for the week-based-year field:

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.

Considering ISO definition (week starts on Monday, and minimum days in first week is 4), week 1 starts on December 29th 2008 and ends in January 4th 2009 (that's the first week that starts on a Monday and it has at least 4 days in 2009), so January 2nd 2009 has a week-based-year equals to 2009 (with ISO definition):

// January 2st 2009
LocalDate dt = LocalDate.of(2009, 1, 2);
System.out.println(dt.get(WeekFields.ISO.weekBasedYear())); // 2009
System.out.println(dt.get(WeekFields.ISO.weekOfWeekBasedYear())); // 1
// WeekFields.ISO and IsoFields are equivalent
System.out.println(dt.get(IsoFields.WEEK_BASED_YEAR)); // 2009
System.out.println(dt.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR)); // 1

But if I consider a WeekFields instance for en_MT locale (English (Malta)), the first day of week is Sunday, and the minimum number of days in first week is 4:

WeekFields wf = WeekFields.of(new Locale("en", "MT"));
System.out.println(wf.getFirstDayOfWeek()); // Sunday
System.out.println(wf.getMinimalDaysInFirstWeek()); // 4
System.out.println(dt.get(wf.weekBasedYear())); // 2008
System.out.println(dt.get(wf.weekOfWeekBasedYear())); // 53

The first week that starts on a Sunday and has at least 4 days in 2009 is the week from January 4th to 10th. So, according to the week definition for en_MT locale, January 2nd 2009 belongs to the 53th week of the week-based-year 2008.

Now, if I take the ar_SA locale (Arabic (Saudi-Arabia)), the week starts on Saturday, and the minimum number of days in first week is 1:

WeekFields wf = WeekFields.of(new Locale("ar", "SA"));
System.out.println(wf.getFirstDayOfWeek()); // Saturday
System.out.println(wf.getMinimalDaysInFirstWeek()); // 1
System.out.println(dt.get(wf.weekBasedYear())); // 2009
System.out.println(dt.get(wf.weekOfWeekBasedYear())); // 1

For this locale, week 1 starts in December 27th 2008 and ends in January 2nd 2009 (it's the first week that starts on a Saturday and has at least 1 day in 2009). So, the week-based-year for January 2nd 2009 in ar_SA locale is also 2009 (the same value I've got using IsoFields, even though the week definition is completely different from ISO).


While IsoFields.WEEK_BASED_YEAR uses ISO's definition, the pattern YYYY will use the WeekFields instance corresponding to the locale set in the formatter (or the JVM default locale, if none is set).

Depending on the definition of each locale (first day of week and minimum number of days in first week), the week-based-year from the localized pattern (YYYY) might have the same value (or not) of the ISO field.

Although it sounds strange that a week can start or end in another year, the javadoc says that it's perfectly valid:

The first and last weeks of a year may contain days from the previous calendar year or next calendar year respectively.


The pattern letters of java.time were based on CLDR (The Unicode Common Locale Data Repository). This link about Week based patterns says:

The year indicated by Y typically begins on the locale’s first day of the week and ends on the last day of the week

Anyway, CLDR is all about localization, so Y is localized as well - as stated by Stephen Colebourne's comment below:

The whole purpose of CLDR is localization, so yes, the "Y" pattern letter is localized. While I understand the desire for a pattern letter that always operates using the ISO rules, it doesn't exist and getting CLDR to add it would be hard to impossible. (Java is following CLDR closely)


My conclusion is, if you want ISO week fields, don't use the localized patterns. Or, as a - not ideal, quite ugly - workaround, use a locale that matches ISO's week definition (in my JVM, Locale.FRENCH does the trick, as WeekFields.ISO.equals(WeekFields.of(Locale.FRENCH)) returns true). The only problem is that the locale will also affect other fields (in case you have month or day of week names, such as MMM or EEE, and any other locale sensitive data).

`uuuu` versus `yyyy` in `DateTimeFormatter` formatting pattern codes in Java?

Within the scope of java.time-package, we can say:

  • It is safer to use "u" instead of "y" because DateTimeFormatter will otherwise insist on having an era in combination with "y" (= year-of-era). So using "u" would avoid some possible unexpected exceptions in strict formatting/parsing. See also this SO-post. Another minor thing which is improved by "u"-symbol compared with "y" is printing/parsing negative gregorian years (in far past).

  • Otherwise we can clearly state that using "u" instead of "y" breaks long-standing habits in Java-programming. It is also not intuitively clear that "u" denotes any kind of year because a) the first letter of the English word "year" is not in agreement with this symbol and b) SimpleDateFormat has used "u" for a different purpose since Java-7 (ISO-day-number-of-week). Confusion is guaranteed - for ever?

  • We should also see that using eras (symbol "G") in context of ISO is in general dangerous if we consider historic dates. If "G" is used with "u" then both fields are unrelated to each other. And if "G" is used with "y" then the formatter is satisfied but still uses proleptic gregorian calendar when the historic date mandates different calendars and date-handling.

Background information:

When developing and integrating the JSR 310 (java.time-packages) the designers decided to use Common Locale Data Repository (CLDR)/LDML-spec as the base of pattern symbols in DateTimeFormatter. The symbol "u" was already defined in CLDR as proleptic gregorian year, so this meaning was adopted to new upcoming JSR-310 (but not to SimpleDateFormat because of backwards compatibility reasons).

However, this decision to follow CLDR was not quite consistent because JSR-310 had also introduced new pattern symbols which didn't and still don't exist in CLDR, see also this old CLDR-ticket. The suggested symbol "I" was changed by CLDR to "VV" and finally overtaken by JSR-310, including new symbols "x" and "X". But "n" and "N" still don't exist in CLDR, and since this old ticket is closed, it is not clear at all if CLDR will ever support it in the sense of JSR-310. Furthermore, the ticket does not mention the symbol "p" (padding instruction in JSR-310, but not defined in CLDR). So we have still no perfect agreement between pattern definitions across different libraries and languages.

And about "y": We should also not overlook the fact that CLDR associates this year-of-era with at least some kind of mixed Julian/Gregorian year and not with the proleptic gregorian year as JSR-310 does (leaving the oddity of negative years aside). So no perfect agreement between CLDR and JSR-310 here, too.

Why does formatting a date using DateTimeFormatter with a YYYY pattern give the wrong year?

This happens because the pattern MM-d-YYYY isn't what we think it is.

If we look at the documentation we see

 Symbol  Meaning                     Presentation      Examples  
------ ------- ------------ -------
y year-of-era year 2004; 04
Y week-based-year year 1996; 96

So YYYY is using the week-based-year, when we actually wanted yyyy, the year-of-era.

See this related question or the Wikipedia article on week dates for the difference.

So the pattern we want is MM-d-yyyy.

getting Monday date from given week and year using java.time package

This is astonishingly more difficult than the partially invalid expectations of OP and most answers show.

First to say: It is very important to define the correct order of week-based-manipulations. The OP has first applied day-manipulation, then year-based manipulation. The correct approach is in reverse! I will show the right helper method implementation:

public static void main(String... args) {
System.out.println(
getDateFromWeekAndYear("53", "2015")); // 2015-12-28, NOT 2014-12-28
System.out.println(
getDateFromWeekAndYear("53", "2015").get(WeekFields.ISO.weekOfWeekBasedYear())); // 53
System.out.println(
getDateFromWeekAndYear("53", "2014")); // 2014-12-29
System.out.println(
getDateFromWeekAndYear("53", "2014").get(WeekFields.ISO.weekOfWeekBasedYear())); // 1
}

private static LocalDate getDateFromWeekAndYear(final String week,final String year) {
int y = Integer.parseInt(year);
LocalDate date = LocalDate.of(y, 7, 1); // safer than choosing current date
// date = date.with(WeekFields.ISO.weekBasedYear(), y); // no longer necessary
date = date.with(WeekFields.ISO.weekOfWeekBasedYear(), Long.parseLong(week));
date = date.with(WeekFields.ISO.dayOfWeek(), 1);

return date;
}

If you don't respect this specific order then you will indeed get sometimes a 2014-date for the input 2015-W53 (depending on the current date).

Second problem: I have also avoided to start with current date in order to be not near start or end of calendar year (calendar year != week-based-year) and instead chosen midth of year as starting point.

The third problem is lenient handling of week 53 in (week-based)-year 2014. It does not exist because 2014 had only 52 weeks!!! A strict algorithm should recognize and reject such an input. Therefore I advise against using YearWeek.of(2014, 53) (in the external library Threeten-Extra) resulting in the first week of 2015, see also its javadoc. Better than such lenient handling would have been

YearWeek yw = YearWeek.of(2014, 52);
if (yw.is53WeekYear()) {
yw = YearWeek.of(2014, 53);
}

or using this code from my own time library Time4J (whose class CalendarWeek has extra i18n-features and extra week arithmetic in comparison with YearWeek):

CalendarWeek.of(2014, 53); // throws an exception
System.out.println(CalendarWeek.of(2014, 1).withLastWeekOfYear()); // 2014-W52

Only using java.time-package:

Using such external libraries would at least have helped to solve the first problem in a transparent way. If you are not willing to add an extra dependency then you can do this to handle week 53 if invalid:

If the expression WeekFields.ISO.weekOfWeekBasedYear() applied on the result of your helper method yields the value 1 then you know that week 53 was invalid. And then you can decide if you want to accept lenient handling or to throw an exception. But silent adjusting such an invalid input is IMHO bad design.

Achieve DateTimeFormatter Y10K output equal to SimpleDateFormat?

ISO 8601 does permit this year format. From Wikipedia:

To represent years before 0000 or after 9999, the standard also permits the expansion of the year representation but only by prior agreement between the sender and the receiver. An expanded year representation [±YYYYY] must have an agreed-upon number of extra year digits beyond the four-digit minimum, and it must be prefixed with a + or − sign instead of the more common AD/BC (or CE/BCE) notation;

However, since it only permits this "by prior agreement between the sender and the receiver", it is quite strange that adding the sign is the default behaviour of LocalDate.toString.

According to the docs:

Year: The count of letters determines the minimum field width below which padding is used. If the count of letters is two, then a reduced two digit form is used. For printing, this outputs the rightmost two digits. For parsing, this will parse using the base value of 2000, resulting in a year within the range 2000 to 2099 inclusive. If the count of letters is less than four (but not two), then the sign is only output for negative years as per SignStyle.NORMAL. Otherwise, the sign is output if the pad width is exceeded, as per SignStyle.EXCEEDS_PAD.

So if you don't want the sign, you can use 3 "y"s, or just 1 "y", since 3 and 1 are both "less than four (but not two)".

Also, since "y" means "year of era", there won't be any negative years, so you don't need to worry about it outputting a sign for negative years either.

Example:

System.out.println(
LocalDate.of(10000, 1, 1)
.format(DateTimeFormatter.ofPattern("dd-MM-yyy"))
); // 01-01-10000

More generally, you can specify the sign style using the appendValue(TemporalField, int, int, SignStyle) method in DateTimeFormatterBuilder. You can specify SignStyle.NEVER to make it never output a sign.



Related Topics



Leave a reply



Submit