Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to parse date from string with year and week using java.time

Tags:

java

java-time

In old java I can do it in that way:

System.out.println(new SimpleDateFormat("yyyy w", Locale.UK).parse("2015 1"));
// shows Mon Dec 29 00:00:00 CET 2014

System.out.println(new SimpleDateFormat("yyyy w", Locale.US).parse("2015 1"));
// shows Mon Dec 28 00:00:00 CET 2014

I would like to use java.time in Java 8.

System.out.println( LocalDate.parse("2015 1", DateTimeFormatter.ofPattern("yyyy w", Locale.US)));

Result:

java.time.format.DateTimeParseException: Text '2015 1' could not be parsed: Unable to obtain LocalDate from TemporalAccessor: {WeekOfWeekBasedYear[WeekFields[SUNDAY,1]]=1, Year=2015},ISO of type java.time.format.Parsed

How to do it in java.time?

Moreover, I'm not satisfied that I have to pass Locale to determine first day of week: Monday vs Sunday. It is not country feature but calendar feature. I would like to use something like java.time.temporal.WeekFields.ISO to show the world that week start with Monday

I found similar case : https://stackoverflow.com/questions/3941700/how-to-get-dates-of-a-week-i-know-week-number

but not for java.time in Java 8. Moreover, solution that first create a date object and later set correct week is not elegant. I want to create final date in one shot.

like image 966
michaldo Avatar asked Jan 13 '15 18:01

michaldo


3 Answers

Direct answer and solution:

System.out.println( 
  LocalDate.parse("2015 1", 
    new DateTimeFormatterBuilder().appendPattern("YYYY w")
    .parseDefaulting(WeekFields.ISO.dayOfWeek(), 1)
    .toFormatter()));
// output: 2014-12-29

Explanations:

a) You should use Y instead of y because you are interested in ISO-8601-week-date, not in year-of-era.

b) A calendar date cannot be formed by just giving a (week-based) year and a week-number. The day of week matters to determine the day within the specified calendar week. The predefined formatter for week-dates requires the missing day-of-week. So you need to construct a specialized parser using the builder-pattern. Then it is necessary to tell the parser what day of week is wanted - via the method parseDefaulting().

c) I insist (and defend JSR-310 here) on saying that the question when a week starts is not a calendar issue but a country-dependent issue. US and France (as example) use the same calendar but have different views how to define a week. ISO-8601-standard can be applied using the explicitly ISO-referring field WeekFields.ISO.dayOfWeek(). Attention: Testing has revealed that using ChronoField.DAY_OF_WEEK together with Locale.ROOT does not always seem to guarantee ISO-week-behaviour as indicated in my first version of this answer (the reasons are not yet clear for me - a close view of the sources seems to be necessary to enlighten the unintuitive behaviour).

d) The java-time-package does it well - with the exception that Monday is just specified as number 1. I would have preferred the enum. Or use the enum and its method getValue().

e) Side notice: SimpleDateFormat behaves leniently by default. The java-time-package is stricter and rejects to invent a missing day-of-week out of thin air - even in lenient mode (which is in my opinion rather a good thing). Software should not guess so much, instead the programmer should think more about what day-of-week is the right one. Again here: The application requirements will probably differ in US and France about the right default setting.

like image 114
Meno Hochschild Avatar answered Oct 09 '22 17:10

Meno Hochschild


The accepted Answer by Meno Hochschild is correct. Here is an alternate route.

YearWeek

Use the YearWeek class in ThreeTen-Extra library. This library extends java.time with additional functionality. This particular class represents a year-week in the ISO 8601 week date system.

The factory method YearWeek.of takes a pair of integer arguments, the week-based-year and the week number.

YearWeek yw = YearWeek.of( 2015 , 1 );  

ISO 8601

Ideally you would be using standard ISO 8601 formats for strings representing date-time values. For year-week that would be yyyy-Www the week-based-year number, a hyphen, a W, and two digits for week number with padding zero as needed.

2015-W01

The java.time classes, and ThreeTen-Extra classes, use the standard ISO 8601 formats by default when parsing and generating strings. So life is much easier if you stick to the standard.

YearWeek yw = YearWeek.parse( "2015-W01" );
String output = yw.toString(); // 2015-W01

Parsing integers.

You have a non-standard format for numbers. So let's parse your string as two pieces, one number each, to be interpreted as int integers.

String string = "2015 1";
String[] parts = string.split(" "); // SPACE character.
String part1 = parts[0]; // 2015
String part2 = parts[1]; // 1

The Integer class converts such strings to int primitive values.

int weekBasedYearNumber = Integer.parseInt( part1 ) ;
int weekNumber = Integer.parseInt( part2 ) ;

Now call that factory method.

YearWeek yw = YearWeek.of( weekBasedYearNumber , weekNumber );

LocalDate

As for first day of week, here we have been discussing the standard ISO 8601 definition of week. In that definition, Monday is always the first day, a week running from Monday-Sunday. Week # 1 contains the first Thursday of the calendar-year. In the case of 2015-W01 that Thursday would be January 1, 2015.

So, no Locale needed.

I am not quite sure of your goal, but it seems to be extracting particular dates for days within your week. That is quite easy with the YearWeek class and the DayOfWeek enum.

LocalDate monday = yw.atDay( DayOfWeek.MONDAY );  // 2014-12-29 start-of-week.
LocalDate friday = yw.atDay( DayOfWeek.FRIDAY );  // 2015-01-02
LocalDate sunday = yw.atDay( DayOfWeek.SUNDAY );  // 2015-01-04 end-of-week.

screen shot of calendar month January 2015 with ISO 8601 week number 1 running from 2014-12-29 Monday to 2015-01-04 Sunday

like image 45
Basil Bourque Avatar answered Oct 09 '22 17:10

Basil Bourque


This can also be achieved by putting default parse value for Day of week using "ChronoField.Day_Of_Week" and setting the value as "1" as follows:

System.out.println( "Date From Year and Week : "+
              LocalDate.parse("2015 1", 
                new DateTimeFormatterBuilder().appendPattern("YYYY w")
                .parseDefaulting(ChronoField.DAY_OF_WEEK, 1)
                .toFormatter()));
like image 24
KayV Avatar answered Oct 09 '22 18:10

KayV