3

I was trying to compute week of year from a ISO-8601 Date format String input. Initially I tried this with java.time.ZonedDateTime but it gives incorrect result for Input Date - 2-Jan-2049. Then I tried with Calendar API it also gives incorrect response for 31-Dec-2049.

I have attached the sample test code

public class ZonedDateTimeTest {        
    public static void main(String[] args) throws InterruptedException {
        System.out.println("======================================");
        String instantStr1 = "2049-01-02T03:48:00Z";
        printYearAndWeekOfYear(instantStr1);
        System.out.println("======================================");
        String instantStr2 = "2049-12-31T03:48:00Z";
        printYearAndWeekOfYear(instantStr2);
        System.out.println("======================================");
    }

    public static void printYearAndWeekOfYear(String ISODate) {
        System.out.println("Date provided -> " + ISODate);

        ZonedDateTime utcTimestamp = parseToInstant(ISODate).atZone(ZoneOffset.UTC);
        int year = utcTimestamp.getYear();
        int weekOfYear = utcTimestamp.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR);
        System.out.println("Using ZonedDateTime API:: Year " + year + " weekOfYear " + weekOfYear);


        Date d1 = Date.from(parseToInstant(ISODate));
        Calendar cl = Calendar.getInstance();
        cl.setTime(d1);
        int year1 = cl.get(Calendar.YEAR);
        int weekOfYear1 = cl.get(Calendar.WEEK_OF_YEAR);
        System.out.println("Using Calendar API:: Year " + year1 + " weekOfYear " + weekOfYear1);
    }

    public static Instant parseToInstant(String ISODate) {
        return DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(ISODate, Instant::from);
    }
}

Output from code above

======================================
Date provided  2049-01-02T03:48:00Z
Using ZonedDateTime API: Year 2049 weekOfYear 53
Using Calendar API: Year 2049 weekOfYear 1    
======================================    
Date provided 2049-12-31T03:48:00Z
Using ZonedDateTime API: Year 2049 weekOfYear 52
Using Calendar API: Year 2049 weekOfYear 1
======================================
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
kanchan
  • 167
  • 2
  • 13
  • 1
    Please put that comment *into the question* - it's part of the question, logically. It's also unfortunate that you haven't specified a time zone anywhere in the `Calendar` part, which could easily affect the results. – Jon Skeet Jan 16 '17 at 09:49
  • 1
    Please stop breaking the formatting. I've fixed your code formatting twice now, and both times you've undone it. I've then reformatted the results so that they're easier to read, and again you've undone it. – Jon Skeet Jan 16 '17 at 10:00

2 Answers2

8

There are four problems with your code to start with:

  • You're using the system default time zone when you use Calendar, which may well change which date the Instant falls on. If you set the calendar to use UTC you'll make it more consistent.
  • You're using Calendar.YEAR which will give you the calendar year rather than the week year. You need to use Calendar.getWeekYear() instead.
  • You're using ZonedDateTime.getYear() which is again the calendar year. You shuold be using utcTimestamp.get(IsoFields.WEEK_BASED_YEAR)
  • You're using Calendar.getInstance() which could give you a non-Gregorian calendar, or it could have first-day-of-week set inappropriately for the computation you want to perform

Fixing these issues (and naming conventions) we end up with:

import java.util.*;
import java.time.*;
import java.time.format.*;
import java.time.chrono.*;
import java.time.temporal.*;

public class ZonedDateTimeTest {

    public static void main(String[] args) {
        printYearAndWeekOfYear("2049-01-02T03:48:00Z");
        String instantStr2 = "2049-12-31T03:48:00Z";
        printYearAndWeekOfYear("2049-12-31T03:48:00Z");
    }

    public static void printYearAndWeekOfYear(String isoDate) {
        System.out.println("Date provided -> " + isoDate);

        Instant instant = DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(isoDate, Instant::from);
        ZonedDateTime utcTimestamp = instant.atZone(ZoneOffset.UTC);
        int year = utcTimestamp.get(IsoFields.WEEK_BASED_YEAR);
        int weekOfYear = utcTimestamp.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR);
        System.out.println("ZonedDateTime: Year " + year + " weekOfYear " + weekOfYear);

        // Force the Gregorian calendar with ISO rules and using UTC
        Calendar calendar = new GregorianCalendar();
        calendar.setFirstDayOfWeek(Calendar.MONDAY);
        calendar.setMinimalDaysInFirstWeek(4);
        calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
        calendar.setTime(Date.from(instant));

        int calYear = calendar.getWeekYear();
        int calWeekOfYear = calendar.get(Calendar.WEEK_OF_YEAR);
        System.out.println("Calendar: Year " + calYear + " weekOfYear " + calWeekOfYear);
        System.out.println();
    }
}

Output:

Date provided -> 2049-01-02T03:48:00Z
ZonedDateTime: Year 2048 weekOfYear 53
Calendar: Year 2048 weekOfYear 53

Date provided -> 2049-12-31T03:48:00Z
ZonedDateTime: Year 2049 weekOfYear 52
Calendar: Year 2049 weekOfYear 52

Both of those look good to me.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • I am interested to know weekOfYear computation. Both ZonedDateTime or Calendar gives possible incorrect response. – kanchan Jan 16 '17 at 10:02
  • @kanchan: No they don't. See my edit - they're both giving the correct ISO week-of-week-year result, but `Calendar` doesn't let you get at the week-year. – Jon Skeet Jan 16 '17 at 10:04
  • The class `GregorianCalendar` has been updated in Java-7 to enable an equivalent solution for the problem, see my answer. – Meno Hochschild Jan 16 '17 at 11:45
  • 1
    @MenoHochschild: Ah, indeed I hadn't spotted `getWeekYear()` in `Calendar`, being so used to providing a field number to `get`... – Jon Skeet Jan 16 '17 at 12:03
2

The old Calendar-stuff indeed enables a solution since Java-7 so I show it as supplement to the Java-8-related answer of Jon Skeet:

String instantStr1 = "2049-01-02T03:48:00Z";
String instantStr2 = "2049-12-31T03:48:00Z";

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
Date d1 = sdf.parse(instantStr1);
Date d2 = sdf.parse(instantStr2);

GregorianCalendar gcal = new GregorianCalendar();
gcal.setFirstDayOfWeek(Calendar.MONDAY);
gcal.setMinimalDaysInFirstWeek(4);

gcal.setTime(d1);
System.out.println(
    "Using Calendar API: Year " + gcal.getWeekYear() + " weekOfYear "
    + gcal.get(Calendar.WEEK_OF_YEAR)
); // Using Calendar API: Year 2048 weekOfYear 53

gcal.setTime(d2);
System.out.println(
    "Using Calendar API: Year " + gcal.getWeekYear() + " weekOfYear "
    + gcal.get(Calendar.WEEK_OF_YEAR)
); // Using Calendar API: Year 2049 weekOfYear 52

For Android-users where this API is standard: The method getWeekYear() is available since API-level 24.

Meno Hochschild
  • 42,708
  • 7
  • 104
  • 126