Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java8 java.time: how to change the day of the week and the start time of the day?

Say I want my week to start on Tuesday, and the day should start at 5:30 am.

This means, code like this should work:

// LocalDateTimes created with the "standard" ISO time
LocalDateTime tuesday_4_30 = LocalDateTime.now()
                                 .with(TemporalAdjusters.next(DayOfWeek.TUESDAY))
                                 .withHour(4).withMinute(30);

LocalDateTime tuesday_6_30    = tuesday_4_30.withHour(6).withMinute(30);
LocalDateTime previous_monday = tuesday_4_30.minusDays(1);

// eventual adjustment using TemporalAdjusters here? like this?
// tuesday_4_30 = tuesday_4_30.with(new MyTemporalAdjuster(DayOfWeek.TUESDAY, 5, 30));
// <do the same for 6_30 and previous monday>
// or possibly change some global parameter like Chronology, Locale, or such..

Assert.assertEquals(tuesday_4_30.getDayOfWeek(), DayOfWeek.MONDAY);

Assert.assertEquals(tuesday_6_30.getDayOfWeek(), DayOfWeek.TUESDAY);

// there is 1 week between the previous monday and the next tuesday 6:30
Assert.assertEquals( ChronoUnit.WEEKS.between(previous_monday,tuesday_6_30), 1);

// there is 0 week between the previous monday and the next tuesday 4:30
Assert.assertEquals( ChronoUnit.WEEKS.between(previous_monday,tuesday_4_30), 0);

// 1 day between tuesday_4_30 and tuesday_6_30
Assert.assertEquals( ChronoUnit.DAYS.between(tuesday_4_30,tuesday_6_30), 1);

// 0 day between previous_monday and tuesday_4_30
Assert.assertEquals( ChronoUnit.DAYS.between(previous_monday,tuesday_4_30), 1);

I am tempted to use temporal adjusters here, and I'm quite sure I could offset the hours and minute so that the day starts at 5:30, but I can't figure out how to modify the start of the week.

Note that I looked into WeekFields but I can't make it work with ChronoUnit.XXX.between(), so I didn't go too far. It looks like I would have to code my own Chronology, which seemed too far strectched.

Can you help me?

like image 721
Gui13 Avatar asked Jun 23 '16 14:06

Gui13


1 Answers

Note: ChronoUnit.WEEKS.between counts the number of entire weeks (a period of 7 days) between two dates. In your case, there is only one days between the Monday and the Tuesday so it will return 0. You probably meant to compare the week of year fields instead.

Unless you want to write your own chronology (that's going to be a pain), you could "fake" your calendar by:

  • converting back and forth between UTC and UTC+5:30 to represent your cut-off time / or just subtract 5:30 from the dates
  • adding some simple logic for the week calculations

See below a rough example based on your code, that makes all the tests pass - you may want to extract the logic into a separate class etc. This is a bit hacky but may be enough for your use case.

@Test
public void test() {
  LocalDateTime tuesday_4_30 = LocalDateTime.now()
                             .with(TemporalAdjusters.next(DayOfWeek.TUESDAY))
                             .withHour(4).withMinute(30);

  LocalDateTime tuesday_6_30    = tuesday_4_30.withHour(6).withMinute(30);
  LocalDateTime previous_monday = tuesday_4_30.minusDays(1);

  // eventual adjustment using TemporalAdjusters here? like this?
  // tuesday_4_30 = tuesday_4_30.with(new MyTemporalAdjuster(DayOfWeek.TUESDAY, 5, 30));
  // <do the same for 6_30 and previous monday>
  // or possibly change some global parameter like Chronology, Locale, or such..
  assertEquals(dayOfWeek(tuesday_4_30), DayOfWeek.MONDAY);

  assertEquals(dayOfWeek(tuesday_6_30), DayOfWeek.TUESDAY);

  // there is 1 week between the previous monday and the next tuesday 6:30
  assertEquals(weekBetween(previous_monday, tuesday_6_30), 1);

  // there is 0 week between the previous monday and the next tuesday 4:30
  assertEquals(weekBetween(previous_monday, tuesday_4_30), 0);

  // 1 day between tuesday_4_30 and tuesday_6_30
  assertEquals(weekBetween(tuesday_4_30, tuesday_6_30), 1);

  // 0 day between previous_monday and tuesday_4_30
  assertEquals(weekBetween(previous_monday, tuesday_4_30), 0);
}

private static DayOfWeek dayOfWeek(LocalDateTime date)  {
  return date.atOffset(ZoneOffset.ofHoursMinutes(5, 30)).withOffsetSameInstant(UTC).getDayOfWeek();
}
private static int weekBetween(LocalDateTime date1, LocalDateTime date2)  {
  OffsetDateTime date1UTC = date1.atOffset(ZoneOffset.ofHoursMinutes(5, 30)).withOffsetSameInstant(UTC);
  OffsetDateTime date2UTC = date2.atOffset(ZoneOffset.ofHoursMinutes(5, 30)).withOffsetSameInstant(UTC);
  int w1 = date1UTC.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR);
  if (dayOfWeek(date1).getValue() >= TUESDAY.getValue()) w1++;

  int w2 = date2UTC.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR);
  if (dayOfWeek(date2).getValue() >= TUESDAY.getValue()) w2++;

  return w2 - w1;
}

Alternative implementation, maybe cleaner:

private static DayOfWeek dayOfWeek(LocalDateTime date)  {
  return adjust(date).getDayOfWeek();
}

private static int weekBetween(LocalDateTime date1, LocalDateTime date2)  {
  int w1 = adjust(date1).get(IsoFields.WEEK_OF_WEEK_BASED_YEAR);
  if (dayOfWeek(date1).getValue() >= TUESDAY.getValue()) w1++;

  int w2 = adjust(date2).get(IsoFields.WEEK_OF_WEEK_BASED_YEAR);
  if (dayOfWeek(date2).getValue() >= TUESDAY.getValue()) w2++;

  return w2 - w1;
}

private static LocalDateTime adjust(LocalDateTime date) {
  return date.minusHours(5).minusMinutes(30);
}
like image 167
assylias Avatar answered Oct 23 '22 15:10

assylias