3

I need to get week span: start of week (Mo) | end of week (Sun) in days of year.

For example, for 5.9.2019, it should be 241-247 (days starting from 0).

However, my method produces 250 | 249

public static Pair<Integer, Integer> getWeekSpan(Timestamp date) {
        Pair<Integer, Integer> result = new Pair<Integer, Integer>();
        Calendar cal = timestampToCalendar(date);

        // start of week
        cal.set(Calendar.DAY_OF_WEEK, cal.getActualMinimum(Calendar.DAY_OF_WEEK));
        result.setKey(cal.get(Calendar.DAY_OF_YEAR) - 1);

        // end of week
        cal.set(Calendar.DAY_OF_WEEK, cal.getActualMaximum(Calendar.DAY_OF_WEEK));
        result.setValue(cal.get(Calendar.DAY_OF_YEAR) - 1);

        return result;
    }
    public static Calendar timestampToCalendar(Timestamp timestamp) {
        Calendar cal = Calendar.getInstance();
        cal.setTimeInMillis(timestamp.getTime());
        return cal;
    }
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
Talos
  • 457
  • 4
  • 15
  • Problem: Calendar class enumerates days in week from Sunday being 1, to Saturday being 7. – Talos Sep 05 '19 at 13:14
  • Ah, right. Well, have a call to setFirstDayOfWeek() first – kumesana Sep 05 '19 at 13:16
  • 4
    I recommend you don’t use `Timestamp` and `Calendar`. Those classes are poorly designed and long outdated. Instead use `Instant` and `LocalDate`, both from [java.time, the modern Java date and time API](https://docs.oracle.com/javase/tutorial/datetime/). – Ole V.V. Sep 05 '19 at 13:32

3 Answers3

4

java.time

public static Pair<Integer, Integer> getWeekSpan(LocalDate date) {
    return new Pair<>(getZeroBasedDayOfYear(date.with(DayOfWeek.MONDAY)),
            getZeroBasedDayOfYear(date.with(DayOfWeek.SUNDAY)));
}

private static int getZeroBasedDayOfYear(LocalDate date) {
    return date.getDayOfYear() - 1;
}

Output:

[244, 250]

It doesn’t agree exactly with what you said you expected, but it does agree with what I get from running the code in your own answer, so I trust that it is correct. The numbers correspond to Monday, September 2 and Sunday, September 8, 2019.

I don’t know whether your Pair class has got a 2-argument constructor. If not, I trust you to fill in the two integers in the same way you did in the question.

Beware that a week may cross New Year. If for instance I specify 2019-12-31, I get:

[363, 4]

4 refers to January 5 of the following year.

My code relies on the fact that you want ISO weeks, where Monday is the first day of the week. If required, we can introduce more flexibility through a WeekFields object:

public static Pair<Integer, Integer> getWeekSpan(LocalDate date) {
    WeekFields wf = WeekFields.ISO;
    return new Pair<>(getZeroBasedDayOfYear(date.with(wf.dayOfWeek(), 1)),
            getZeroBasedDayOfYear(date.with(wf.dayOfWeek(), 7)));
}

As long as I use WeekFields.ISO, the result is still the same, but this is where you might plug in, for example WeekFields.of(Locale.getDefault()) if you wanted to use the week definition of the default locale.

The classes you were using, Timestamp and Calendar, are poorly designed and long outdated. I recommend you don’t use them. Instead I am using LocalDate from java.time, the modern Java date and time API.

If you’ve got a Timestamp object from a legacy API that you cannot change or don’t want to upgrade just now, the way to convert to a LocalDate is:

    LocalDate convertedLocalDate = yourTimestamp.toInstant()
            .atZone(ZoneId.systemDefault())
            .toLocalDate();

What went wrong in your code?

This is one of the places where the Calendar class is getting confusing. It numbers the days of the week from 1 for Sunday through 7 for Saturday. So when you ask for the actual minimum and maximum, you get 1 for Sunday and 7 for Saturday even though these are not the first and the last day of the week. So what happened was that you first set the day of week to Sunday, yielding September 8 because your week starts on Monday. BTW, Calendar.getInstance() picks up the week definition from your default locale, which cannot be read from the code and is likely to confuse many too. And even more so if one day your code runs on a JVM with a different default locale and suddenly behaves differently. Next when setting the day of the week to max, 7, you got Saturday, September 7. The dates were translated correctly to 250 and 249.

Link

Oracle tutorial: Date Time explaining how to use java.time.

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
1

Converting between legacy and modern date-time classes

If you must start with a java.sql.Timestamp because of interoperating with old code not yet updated to java.time classes, you can convert back and forth.

Instant

You will find new to…/from… conversion methods added to the old classes. The java.sql.Timestamp class offers the toInstant method to render a java.time.Instant object. Both the old and new classes represent a moment in UTC with a resolution of nanoseconds.

Instant instant = myJavaUtilTimestamp.toInstant() ;

The next problem is that we have a moment in UTC, a point on the timeline. But your business problem requires a date on the calendar. The trick is that for any given moment the date varies around the globe by time zone. For example, a few minutes after midnight in Paris France is a new day while still “yesterday” in Montréal Québec.

ZonedDateTime

So you must specify the time zone by which you want determine a date. Apply a ZoneId to get a ZonedDateTime object.

ZoneId z = ZoneId.of( "America/Montreal" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;

LocalDate

The zdt represents the very same simultaneous moment as the instant. The difference is that the zdt is viewed through the offset used by the people of that time zone’s region. We can therefore extract the date (year-month-day) from that ZonedDateTime as seen by the people of Québec at that moment, given this example. The date-only value is represented by the LocalDate class, having no time-of-day and no time zone.

LocalDate localDate = zdt.toLocalDate() ;

Now, with this localDate object in hand, you may follow the instructions in this Answer by Ole V.V.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
0

Setting straignt Monday and Sunday solved the problem.

public static Pair<Integer, Integer> getWeekSpan(Timestamp date) {
        Pair<Integer, Integer> result = new Pair<Integer, Integer>();
        Calendar cal = timestampToCalendar(date);

        // start of week
        cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
        result.setKey(cal.get(Calendar.DAY_OF_YEAR) - 1);

        // end of week
        cal.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
        result.setValue(cal.get(Calendar.DAY_OF_YEAR) - 1);

        return result;
    }

EDIT: However this piece of code does solve the initial problem, it does not solve time zone problem and uses deprecated classes. See other answers for more detail.

Talos
  • 457
  • 4
  • 15
  • I came to a project where there was already ton of code and logic based on these legacy classes. Just have to deal with it :( – Talos Sep 06 '19 at 08:28
  • 1
    **Use only the *java.time* classes** when writing new code. You can easily convert back-and-forth between the legacy and modern classes. I added [this Answer](https://stackoverflow.com/a/57826911/642706) to show you the steps in converting. The result is a `LocalDate` which you can use in following the steps shown in [the Answer by Ole V.V.](https://stackoverflow.com/a/57809722/642706) The legacy date-time classes really are so bloody awful that you should avoid them whenever possible. – Basil Bourque Sep 06 '19 at 18:25
  • Another problem with the code in this Answer is that it ignores the **crucial issue of time zone**. Your `Calendar` object here will implicitly use the JVM’s current default time zone. That means **your results may vary**. For dependable results, specify a time zone explicitly as shown in [my Answer](https://stackoverflow.com/a/57826911/642706). – Basil Bourque Sep 06 '19 at 18:28