-1

I've experienced unpleasant problems with Java Calendar. I want to get first day of some week as Date, it is working well until the week is in between years. How to get around it?

Calendar cal = Calendar.getInstance();
cal.set(2017,0,1);
cal.setFirstDayOfWeek(Calendar.MONDAY);
Calendar first = (Calendar) cal.clone();
first.add(Calendar.DAY_OF_WEEK,
first.getFirstDayOfWeek() - first.get(Calendar.DAY_OF_WEEK));
Calendar last = (Calendar) first.clone();
last.add(Calendar.DAY_OF_YEAR, 6);
System.out.println(first.getTime());
System.out.println(last.getTime());

Output

Mon Jan 02 22:30:26 GMT 2017

Sun Jan 08 22:30:26 GMT 2017

The output should be

Mon Dec 26 22:30:26 GMT 2016

Sun Jan 01 22:30:26 GMT 2017

Community
  • 1
  • 1
Tuby
  • 3,158
  • 2
  • 17
  • 36
  • 4
    Why do you believe `first.getFirstDayOfWeek() - first.get(Calendar.DAY_OF_WEEK)` is the way to adjust to beginning of the week? When firstDayOfWeek is 2 (MONDAY) and dayOfWeek is 1 (SUNDAY), you get `2 - 1 = +1`, i.e. you *add* 1 day. That is what you're seeing. I'll leave it to you to figure out how to adjust this logic to fix the issue. BTW: Simply printing those values would have told you this. – Andreas Jan 10 '17 at 14:59
  • 1
    The problem is not because of the change of the year. Your understanding of the numbers of the DayOfWeek is wrong. Even if you set the FirstDayOfWeek to Monday, the number of Monday is 2 and the number of the DayOfWeek from January 1st, 2017 is Sunday/1. So 2-1=1 and January 1st, 2017 +1 = January 2nd, 2017. And January 2nd, 2017 + 6 = January 8, 2017. So everything works as expected. – Niklas P Jan 10 '17 at 15:02
  • Ok thanks for help, I'll use Joda Time API because it is better implemented for my particular problems – Tuby Jan 10 '17 at 15:05
  • 3
    just use the java.time package in java 8. there's no need for joda time anymore – MeBigFatGuy Jan 10 '17 at 16:00

2 Answers2

2

Don't use Calendar, it's known to be a very bad API to use (see this answer). Use the time api instead (if you're not on java8, use the ThreeTen backport).

With that, your code would look like this:

LocalDate ld = LocalDate.of(2017,1,1);
ld = ld.with(ChronoField.DAY_OF_WEEK, 1);
System.out.println(ld);
System.out.println(ld.plusDays(6));

If you do need to end up with a Date, use Date.from(ld.atStartOfDay().atZone(ZoneId.of("UTC")).toInstant()) to convert it (assuming UTC, easy enough to replace by the zone you need)

Community
  • 1
  • 1
Joeri Hendrickx
  • 16,947
  • 4
  • 41
  • 53
  • No, it’s not a “very bad API to use.” It’s a good API, as evidenced by how quickly everyone recognized what the OP was doing wrong. – VGR Jan 10 '17 at 16:24
  • Even if running Java 6 or 7, no need for Joda-Time. Much of the java.time functionality is back-ported in the [ThreeTen-Extra](http://www.threeten.org/threetenbp/) project, and further adapted for Android in the [ThreeTenABP](https://github.com/JakeWharton/ThreeTenABP) project. The Joda-Time project is now in maintenance mode and advises migration to java.time. – Basil Bourque Jan 10 '17 at 18:07
  • @BasilBourque Thanks, I didn't know that. Adapted the answer. – Joeri Hendrickx Jan 11 '17 at 09:03
2

tl;dr

The accepted Answer by Hendrickx is correct. Here is an alternative that I think is a bit more readable.

LocalDate.of( 2017 , Month.JANUARY , 1 )
    .with( TemporalAdjusters.previous( DayOfWeek.MONDAY ) )

TemporalAdjuster

The TemporalAdjuster interface provides classes that manipulate java.time objects. Implementations can be found in the TemporalAdjusters class. The previousOrSame adjuster finds the previous occurrence of a day-of-week or sticks with the same date if it already is that day-of-week.

LocalDate target = LocalDate.of( 2017 , Month.JANUARY , 1 );
LocalDate ld = target.with( TemporalAdjusters.previousOrSame( DayOfWeek.MONDAY ) );

target.toString(): 2017-01-01 | ld.toString(): 2016-12-26

If you always want the previous Monday, even if the date is already a Monday, then use the adjuster previous.

LocalDate ld = target.with( TemporalAdjusters.previous( DayOfWeek.MONDAY ) );

To get the end of the week, use the nextOrSame adjuster.

LocalDate nextOrSameSunday = target.with( TemporalAdjusters.nextOrSame( DayOfWeek.SUNDAY ) );

You can see the day-of-week in a string.

ZoneId z = ZoneId.of( "America/Montreal" );
DateTimeFormatter f = DateTimeFormatter.ofLocalizedDate( FormatStyle.FULL ).withLocale( Locale.CANADA );
System.out.println( "target: " + target.format( f ) + " | ld: " + ld.format( f ) );

target: Sunday, January 1, 2017 | ld: Monday, December 26, 2016

See this code live in IdeOne.com.

Also notice the use of enums, DayOfWeek and Month. See Tutorials.


About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

Where to obtain the java.time classes?

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

Community
  • 1
  • 1
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • Thanks for really detailed answer, I tried using Java 8 time in my android project but it requires latest API's. I did solve my problem with Joda Time and it was really minor, just 1 line of code. You mentioned ThreeTenBackport, I think in my particular case it is not worth making transition to that Backport lib while it is so minor. – Tuby Jan 10 '17 at 21:38
  • 1
    If your needs really are only one line of code, good enough. Usually people doing date-time work are doing a lot of it. If that becomes the case, reread the last section of my Answer more carefully. The back-port for Java 6 & 7 is one project (ThreeTen-Backport), and that project was adapted to Android specifically in another project (ThreeTenABP). The old legacy classes really are so bad that adding a library to supplant them is well worth the effort. – Basil Bourque Jan 10 '17 at 21:45