fvdalcin
fvdalcin

Reputation: 1047

Strange behaviour of ww SimpleDateFormat

Can anyone explain why do I get those values while trying to parse a date? I've tried three different inputs, as follows:

1) Third week of 2013

Date date = new SimpleDateFormat("ww.yyyy").parse("02.2013");
Calendar cal = Calendar.getInstance();
cal.setTime(date);
System.out.println(cal.get(Calendar.WEEK_OF_YEAR) + "." + cal.get(Calendar.YEAR));

Which outputs: 02.2013 (as I expected)

2) First week of 2013

Date date = new SimpleDateFormat("ww.yyyy").parse("00.2013");
Calendar cal = Calendar.getInstance();
cal.setTime(date);
System.out.println(cal.get(Calendar.WEEK_OF_YEAR) + "." + cal.get(Calendar.YEAR));

Which outputs: 52.2012 (which is fine for me, since the first week of 2013 is also the last one of 2012)

3) Second week of 2013

Date date = new SimpleDateFormat("ww.yyyy").parse("01.2013");
Calendar cal = Calendar.getInstance();
cal.setTime(date);
System.out.println(cal.get(Calendar.WEEK_OF_YEAR) + "." + cal.get(Calendar.YEAR));

Which outputs: 1.2012 (which makes absolutely no sense to me)

Does anyone know why this happens?? I need to parse a date in the format (week of year).(year). Am I using the wrong pattern?

Upvotes: 7

Views: 779

Answers (3)

Arvind Kumar Avinash
Arvind Kumar Avinash

Reputation: 79085

java.time

In March 2014, Java 8 introduced the modern, java.time date-time API which supplanted the error-prone legacy java.util date-time API. Any new code should use the java.time API.

java.time is based on ISO 8601 standard where month number, week number etc. start with the natural number 1, unlike the confusing java.util date-time API where they begin with 0. Any attempt to parse a date-time with the month/week/day-of-week number 0 using java.time API throws an exception, giving you a way to handle it as per your business requirement.

...which is fine for me, since the first week of 2013 is also the last one of 2012

No, the same week can not fall in two years.

Solution using java.time API

While you can build a DateTimeFormatter to parse your strings as they are, an easier way would be converting your strings into the format used by DateTimeFormatter#ISO_WEEK_DATE with 1 as the day-of-the-week and parse them into LocalDate instances.

Finally, get the week numbers from the LocalDate instances. Note that the week number is Locale-sensitive e.g. a week starts on Sunday in the US but in the UK, it starts on Monday. Check the US Calendar 2025 and the UK Calendar 2025 to find the difference.

Demo:

class Main {
    public static void main(String[] args) {
        Locale locale = Locale.US;
        String[] arr = { "01.2013", "02.2013", "03.2013", "00.2013" };

        for (String str : arr) {
            str = str.replaceAll("(\\d{2})(\\.)(\\d{4})", "$3-W$1-1");
            try {
                LocalDate date = LocalDate.parse(str, 
                                 DateTimeFormatter.ISO_WEEK_DATE);
                int weekNum = date.get(WeekFields.of(locale).weekOfWeekBasedYear());
                System.out.println("Week number: " + weekNum);
            } catch (DateTimeParseException e) {
                System.out.println("Parsing error: " + e.getMessage());
            }
        }
    }
}

Output:

Week number: 1
Week number: 2
Week number: 3
Parsing error: Text '2013-W00-1' could not be parsed: Invalid value for
                                     WeekOfWeekBasedYear (valid values 1 - 52): 0

Try it on Ideone

The regex pattern: There are three capture-groups in the above regex e.g. (01)(.)(2013) for 01.2013. I have used capture-group numbers 1 and 3 (denoted by $1 and $3 in the code) to get the final string to be parsed.

Learn more about the modern Date-Time API from Trail: Date Time.

Update

The code using the DateTimeFormatter from the comment posted by Anonymous will be as follows:

class Main {
    private static final DateTimeFormatter FORMATTER = new DateTimeFormatterBuilder()
            .appendPattern("ww.YYYY")
            .parseDefaulting(ChronoField.DAY_OF_WEEK, 1)
            .toFormatter(Locale.ROOT);

    public static void main(String[] args) {
        Locale locale = Locale.US;
        String[] arr = { "01.2013", "02.2013", "03.2013", "00.2013" };

        for (String str : arr) {
            try {
                LocalDate date = LocalDate.parse(str, FORMATTER);
                int weekNum = date.get(WeekFields.of(locale).weekOfWeekBasedYear());
                System.out.println("Week number: " + weekNum);
            } catch (DateTimeParseException e) {
                System.out.println("Parsing error: " + e.getMessage());
            }
        }
    }
}

The output will be the same.

Try it on Ideone

Upvotes: 3

endragor
endragor

Reputation: 368

Use Calendar.getWeekYear() to get year value synced with Calendar.WEEK_OF_YEAR field. There is more information about Week of Year and Week Year at the GregorianCalendar doc.

Upvotes: 2

Jon Skeet
Jon Skeet

Reputation: 1500565

You're using ww, which is "week of week-year", but then yyyy which is "calendar year" rather than "week year". Setting the week-of-week-year and then setting the calendar year is a recipe for problems, because they're just separate numbering systems, effectively.

You should be using YYYY in your format string to specify the week-year... although unfortunately it looks like you can't then get the value in a sane way. (I'd expect a Calendar.WEEKYEAR constant, but there is no such thing.)

Also, week-of-year values start at 1, not 0... and no week is in two week-years; it's either the first week of 2013 or it's the last week of 2012... it's not both.

I would personally avoid using week-years and weeks if you possibly can - they can be very confusing, particularly when a date in one calendar year is in a different week year.

Upvotes: 16

Related Questions