tl;dr
Never use Calendar
, now legacy, supplanted by java.time classes such as ZonedDateTime
.
Use a purpose-built class, YearWeek
from the ThreeTen-Extra project, to track standard ISO 8601 weeks.
Custom formatter
Define a DateTimeFormatter
object to match your non-standard input string.
org.threeten.extra.YearWeek
.parse(
"202001" ,
new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.appendValue( IsoFields.WEEK_BASED_YEAR, 4, 10, SignStyle.EXCEEDS_PAD)
.appendValue(IsoFields.WEEK_OF_WEEK_BASED_YEAR, 2)
.toFormatter()
)
.toString()
2020-W01
Standard formatter
Or manipulate your input string to comply with the ISO 8601 standard format, inserting a -W
in the middle between the week-based-year and the week. The java.time classes and the ThreeTen-Extra classes all use the ISO 8601 formats by default when parsing/generating strings.
String input = "202001";
String inputModified = input.substring( 0 , 4 ) + "-W" + input.substring( 4 );
YearWeek yearWeek = YearWeek.parse( inputModified ) ;
yearWeek.toString(): 2020-W01
Avoid legacy date-time classes
Do not waste your time trying to understand Calendar
. This terrible class was supplanted years ago by the modern java.time classes defined in JSR 310.
Definition of week
You must specify your definition of a week. Do you mean week number 1 contains the first day of the year? Or week # 1contains a certain day of the week? Or week # 1 is the first calendar week to consist entirely of dates in the new year? Or perhaps an industry-specific definition of week? Some other definition?
One of the confusing things about Calendar
is that its definition of a week shifts by Locale
. This one of many reasons to avoid that legacy class.
Week-based year
Depending on your definition of week, the year of a week may not be the calendar year of some dates on that week. A week-based year may overlap with calendar years.
Standard weeks and week-based year
For example, the standard ISO 8601 week defines a week as:
- Starting on Monday, and
- Week # 1 contains the first Thursday of the calendar year.
So there are 52 or 53 whole weeks in every week-based year. Of course, that means some dates from the previous and/or following calendar years may appear in the first/last weeks of our week-based year.
org.threeten.extra.YearWeek
One problem is that you are trying to represent a year-week with a class that represents a moment, a date with time of day in the context of a time zone.
Instead, use a purpose-built class. You can find one in the ThreeTen-Extra library, YearWeek
. This library extends the functionality of the java.time classes built into Java 8 and later.
With that class I would think that we could define a DateTimeFormatter
to parse your input using the formatting pattern YYYYww
where the YYYY
means a 4-digit year of week-based-year, and the ww
means the two-digit week number. Like this:
// FAIL
String input = "202001" ;
DateTimeFormatter f = DateTimeFormatter.ofPattern( "YYYYww" ) ;
YearWeek yearWeek = YearWeek.parse( input , f ) ;
But using that formatter throws an DateTimeParseException
for reasons that escape me.
Exception in thread "main" java.time.format.DateTimeParseException: Text '202001' could not be parsed: Unable to obtain YearWeek from TemporalAccessor: {WeekOfWeekBasedYear[WeekFields[SUNDAY,1]]=1, WeekBasedYear[WeekFields[SUNDAY,1]]=2020},ISO of type java.time.format.Parsed
…
Caused by: java.time.DateTimeException: Unable to obtain YearWeek from TemporalAccessor: {WeekOfWeekBasedYear[WeekFields[SUNDAY,1]]=1, WeekBasedYear[WeekFields[SUNDAY,1]]=2020},ISO of type java.time.format.Parsed
…
Caused by: java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: WeekBasedYear
Alternatively, we can use DateTimeFormatterBuilder
to build up a DateTimeFormatter
from parts. By perusing the OpenJDK source code for Java 13 for DateTimeFormatter.ISO_WEEK_DATE
I was able to cobble together this formatter that seems to work.
DateTimeFormatter f =
new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.appendValue( IsoFields.WEEK_BASED_YEAR, 4, 10, SignStyle.EXCEEDS_PAD)
.appendValue(IsoFields.WEEK_OF_WEEK_BASED_YEAR, 2)
.toFormatter()
;
Using that:
String input = "202001" ;
YearWeek yearWeek = YearWeek.parse( input , f ) ;
ISO 8601
Educate the publisher of your data about the ISO 8601 standard defining formats for representing date-time values textually.
To generate a string in standard format representing the value of our YearWeek
, call toString
.
String output = yearWeek.toString() ;
2020-W01
And parsing a standard string.
YearWeek yearWeek = YearWeek.parse( "2020-W01" ) ;