TSK-1972: Uses time zone for working time calculations
This commit is contained in:
parent
37280cc73b
commit
b916d577ca
|
@ -8,6 +8,7 @@ import java.lang.reflect.Type;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.LocalTime;
|
import java.time.LocalTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -52,6 +53,7 @@ public class TaskanaConfigurationInitializer {
|
||||||
PROPERTY_INITIALIZER_BY_CLASS.put(Instant.class, new InstantPropertyParser());
|
PROPERTY_INITIALIZER_BY_CLASS.put(Instant.class, new InstantPropertyParser());
|
||||||
PROPERTY_INITIALIZER_BY_CLASS.put(List.class, new ListPropertyParser());
|
PROPERTY_INITIALIZER_BY_CLASS.put(List.class, new ListPropertyParser());
|
||||||
PROPERTY_INITIALIZER_BY_CLASS.put(Map.class, new MapPropertyParser());
|
PROPERTY_INITIALIZER_BY_CLASS.put(Map.class, new MapPropertyParser());
|
||||||
|
PROPERTY_INITIALIZER_BY_CLASS.put(ZoneId.class, new ZoneIdPropertyParser());
|
||||||
PROPERTY_INITIALIZER_BY_CLASS.put(Enum.class, new EnumPropertyParser());
|
PROPERTY_INITIALIZER_BY_CLASS.put(Enum.class, new EnumPropertyParser());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -432,6 +434,18 @@ public class TaskanaConfigurationInitializer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class ZoneIdPropertyParser implements PropertyParser<ZoneId> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<ZoneId> initialize(
|
||||||
|
Map<String, String> properties,
|
||||||
|
String separator,
|
||||||
|
Field field,
|
||||||
|
TaskanaProperty taskanaProperty) {
|
||||||
|
return parseProperty(properties, taskanaProperty.value(), ZoneId::of);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static class EnumPropertyParser implements PropertyParser<Enum<?>> {
|
static class EnumPropertyParser implements PropertyParser<Enum<?>> {
|
||||||
@Override
|
@Override
|
||||||
public Optional<Enum<?>> initialize(
|
public Optional<Enum<?>> initialize(
|
||||||
|
|
|
@ -6,8 +6,10 @@ import java.time.Instant;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.LocalTime;
|
import java.time.LocalTime;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneId;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.SortedSet;
|
import java.util.SortedSet;
|
||||||
|
@ -18,31 +20,36 @@ import pro.taskana.common.api.exceptions.InvalidArgumentException;
|
||||||
|
|
||||||
public class WorkingTimeCalculatorImpl implements WorkingTimeCalculator {
|
public class WorkingTimeCalculatorImpl implements WorkingTimeCalculator {
|
||||||
|
|
||||||
static final ZoneOffset UTC = ZoneOffset.UTC;
|
private final ZoneId zoneId;
|
||||||
|
|
||||||
private final HolidaySchedule holidaySchedule;
|
private final HolidaySchedule holidaySchedule;
|
||||||
private final WorkingTimeSchedule workingTimeSchedule;
|
private final WorkingTimeSchedule workingTimeSchedule;
|
||||||
|
|
||||||
public WorkingTimeCalculatorImpl(
|
public WorkingTimeCalculatorImpl(
|
||||||
HolidaySchedule holidaySchedule, Map<DayOfWeek, Set<LocalTimeInterval>> workingTimeSchedule) {
|
HolidaySchedule holidaySchedule,
|
||||||
|
Map<DayOfWeek, Set<LocalTimeInterval>> workingTimeSchedule,
|
||||||
|
ZoneId zoneId) {
|
||||||
this.holidaySchedule = holidaySchedule;
|
this.holidaySchedule = holidaySchedule;
|
||||||
this.workingTimeSchedule = new WorkingTimeSchedule(workingTimeSchedule);
|
this.workingTimeSchedule = new WorkingTimeSchedule(workingTimeSchedule);
|
||||||
|
this.zoneId = Objects.requireNonNull(zoneId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Instant subtractWorkingTime(Instant workStart, Duration workingTime)
|
public Instant subtractWorkingTime(Instant workStart, Duration workingTime)
|
||||||
throws InvalidArgumentException {
|
throws InvalidArgumentException {
|
||||||
validatePositiveDuration(workingTime);
|
validatePositiveDuration(workingTime);
|
||||||
WorkSlot workSlot = getWorkSlotOrPrevious(toLocalDateTime(workStart));
|
ZonedDateTime workStartInTimeZone = toZonedDateTime(workStart);
|
||||||
return workSlot.subtractWorkingTime(workStart, workingTime);
|
WorkSlot workSlot = getWorkSlotOrPrevious(workStartInTimeZone);
|
||||||
|
return workSlot.subtractWorkingTime(workStartInTimeZone, workingTime).toInstant();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Instant addWorkingTime(Instant workStart, Duration workingTime)
|
public Instant addWorkingTime(Instant workStart, Duration workingTime)
|
||||||
throws InvalidArgumentException {
|
throws InvalidArgumentException {
|
||||||
validatePositiveDuration(workingTime);
|
validatePositiveDuration(workingTime);
|
||||||
WorkSlot bestMatchingWorkSlot = getWorkSlotOrNext(toLocalDateTime(workStart));
|
ZonedDateTime workStartInTimeZone = toZonedDateTime(workStart);
|
||||||
return bestMatchingWorkSlot.addWorkingTime(workStart, workingTime);
|
WorkSlot workSlot = getWorkSlotOrNext(workStartInTimeZone);
|
||||||
|
return workSlot.addWorkingTime(workStartInTimeZone, workingTime).toInstant();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -60,24 +67,7 @@ public class WorkingTimeCalculatorImpl implements WorkingTimeCalculator {
|
||||||
to = second;
|
to = second;
|
||||||
}
|
}
|
||||||
|
|
||||||
WorkSlot bestMatchingWorkSlot = getWorkSlotOrNext(toLocalDateTime(from));
|
return calculateWorkingTime(toZonedDateTime(from), toZonedDateTime(to));
|
||||||
Instant earliestWorkStart = max(from, bestMatchingWorkSlot.start);
|
|
||||||
Instant endOfWorkSlot = bestMatchingWorkSlot.end;
|
|
||||||
|
|
||||||
if (endOfWorkSlot.compareTo(to) >= 0) {
|
|
||||||
if (bestMatchingWorkSlot.start.compareTo(to) <= 0) {
|
|
||||||
// easy part. _from_ and _to_ are in the same work slot
|
|
||||||
return Duration.between(earliestWorkStart, to);
|
|
||||||
} else {
|
|
||||||
// _from_ and _to_ are before the bestMatchingWorkSlot aka between two work slots. We simply
|
|
||||||
// drop it
|
|
||||||
return Duration.ZERO;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Take the current duration and add the working time starting after this work slot.
|
|
||||||
return Duration.between(earliestWorkStart, endOfWorkSlot)
|
|
||||||
.plus(workingTimeBetween(endOfWorkSlot, to));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -101,6 +91,29 @@ public class WorkingTimeCalculatorImpl implements WorkingTimeCalculator {
|
||||||
return holidaySchedule.isGermanHoliday(toLocalDate(instant));
|
return holidaySchedule.isGermanHoliday(toLocalDate(instant));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Duration calculateWorkingTime(ZonedDateTime from, ZonedDateTime to)
|
||||||
|
throws InvalidArgumentException {
|
||||||
|
|
||||||
|
WorkSlot bestMatchingWorkSlot = getWorkSlotOrNext(from);
|
||||||
|
ZonedDateTime earliestWorkStart = max(from, bestMatchingWorkSlot.start);
|
||||||
|
ZonedDateTime endOfWorkSlot = bestMatchingWorkSlot.end;
|
||||||
|
|
||||||
|
if (endOfWorkSlot.compareTo(to) >= 0) {
|
||||||
|
if (bestMatchingWorkSlot.start.compareTo(to) <= 0) {
|
||||||
|
// easy part. _from_ and _to_ are in the same work slot
|
||||||
|
return Duration.between(earliestWorkStart, to);
|
||||||
|
} else {
|
||||||
|
// _from_ and _to_ are before the bestMatchingWorkSlot aka between two work slots. We simply
|
||||||
|
// drop it
|
||||||
|
return Duration.ZERO;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Take the current duration and add the working time starting after this work slot.
|
||||||
|
return Duration.between(earliestWorkStart, endOfWorkSlot)
|
||||||
|
.plus(calculateWorkingTime(endOfWorkSlot, to));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void validateNonNullInstants(Instant first, Instant second) {
|
private void validateNonNullInstants(Instant first, Instant second) {
|
||||||
if (first == null || second == null) {
|
if (first == null || second == null) {
|
||||||
throw new InvalidArgumentException("Neither first nor second may be null.");
|
throw new InvalidArgumentException("Neither first nor second may be null.");
|
||||||
|
@ -118,11 +131,11 @@ public class WorkingTimeCalculatorImpl implements WorkingTimeCalculator {
|
||||||
* within a WorkSlot that WorkSlot is returned, if currentDateTime is not within a WorkSlot the
|
* within a WorkSlot that WorkSlot is returned, if currentDateTime is not within a WorkSlot the
|
||||||
* next WorkSlot is returned.
|
* next WorkSlot is returned.
|
||||||
*
|
*
|
||||||
* @param currentDateTime The LocalDateTime we want the best matching WorkSlot for. May not be
|
* @param currentDateTime The ZonedDateTime we want the best matching WorkSlot for. May not be
|
||||||
* <code>null</code>.
|
* <code>null</code>.
|
||||||
* @return The WorkSlot that matches best <code>currentDateTime</code> if we want to add.
|
* @return The WorkSlot that matches best <code>currentDateTime</code> if we want to add.
|
||||||
*/
|
*/
|
||||||
private WorkSlot getWorkSlotOrNext(LocalDateTime currentDateTime) {
|
private WorkSlot getWorkSlotOrNext(ZonedDateTime currentDateTime) {
|
||||||
LocalDate currentDate = currentDateTime.toLocalDate();
|
LocalDate currentDate = currentDateTime.toLocalDate();
|
||||||
// We do not work on Holidays
|
// We do not work on Holidays
|
||||||
if (holidaySchedule.isHoliday(currentDate)) {
|
if (holidaySchedule.isHoliday(currentDate)) {
|
||||||
|
@ -149,11 +162,11 @@ public class WorkingTimeCalculatorImpl implements WorkingTimeCalculator {
|
||||||
* within a WorkSlot that WorkSlot is returned, if currentDateTime is not within a WorkSlot the
|
* within a WorkSlot that WorkSlot is returned, if currentDateTime is not within a WorkSlot the
|
||||||
* previous WorkSlot is returned.
|
* previous WorkSlot is returned.
|
||||||
*
|
*
|
||||||
* @param currentDateTime The LocalDateTime we want the best matching WorkSlot for. May not be
|
* @param currentDateTime The ZonedDateTime we want the best matching WorkSlot for. May not be
|
||||||
* <code>null</code>.
|
* <code>null</code>.
|
||||||
* @return The WorkSlot that matches best <code>currentDateTime</code> if we want to subtract.
|
* @return The WorkSlot that matches best <code>currentDateTime</code> if we want to subtract.
|
||||||
*/
|
*/
|
||||||
private WorkSlot getWorkSlotOrPrevious(LocalDateTime currentDateTime) {
|
private WorkSlot getWorkSlotOrPrevious(ZonedDateTime currentDateTime) {
|
||||||
LocalDate currentDate = currentDateTime.toLocalDate();
|
LocalDate currentDate = currentDateTime.toLocalDate();
|
||||||
// We do not work on Holidays
|
// We do not work on Holidays
|
||||||
if (holidaySchedule.isHoliday(currentDate)) {
|
if (holidaySchedule.isHoliday(currentDate)) {
|
||||||
|
@ -177,31 +190,41 @@ public class WorkingTimeCalculatorImpl implements WorkingTimeCalculator {
|
||||||
getWorkSlotOrPrevious(getDayBefore(currentDateTime)));
|
getWorkSlotOrPrevious(getDayBefore(currentDateTime)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isBeforeOrEquals(LocalTime time, LocalDateTime currentDateTime) {
|
private static boolean isBeforeOrEquals(LocalTime time, ZonedDateTime currentDateTime) {
|
||||||
return !time.isAfter(currentDateTime.toLocalTime());
|
return !time.isAfter(currentDateTime.toLocalTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
private LocalDateTime getDayAfter(LocalDateTime current) {
|
private ZonedDateTime getDayAfter(ZonedDateTime current) {
|
||||||
return LocalDateTime.of(current.toLocalDate().plusDays(1), LocalTime.MIN);
|
return LocalDateTime.of(current.toLocalDate().plusDays(1), LocalTime.MIN)
|
||||||
}
|
.atZone(current.getZone());
|
||||||
|
|
||||||
private LocalDateTime getDayBefore(LocalDateTime current) {
|
|
||||||
return LocalDateTime.of(current.toLocalDate().minusDays(1), LocalTime.MAX);
|
|
||||||
}
|
|
||||||
|
|
||||||
private LocalDateTime toLocalDateTime(Instant instant) {
|
|
||||||
return LocalDateTime.ofInstant(instant, UTC);
|
|
||||||
}
|
|
||||||
|
|
||||||
private LocalDate toLocalDate(Instant instant) {
|
|
||||||
return LocalDate.ofInstant(instant, UTC);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private DayOfWeek toDayOfWeek(Instant instant) {
|
private DayOfWeek toDayOfWeek(Instant instant) {
|
||||||
return toLocalDate(instant).getDayOfWeek();
|
return toLocalDate(instant).getDayOfWeek();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Instant max(Instant a, Instant b) {
|
private ZonedDateTime getDayBefore(ZonedDateTime current) {
|
||||||
|
return LocalDateTime.of(current.toLocalDate().minusDays(1), LocalTime.MAX)
|
||||||
|
.atZone(current.getZone());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ZonedDateTime toZonedDateTime(Instant instant) {
|
||||||
|
return instant.atZone(zoneId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ZonedDateTime toZonedDateTime(LocalDateTime localDateTime) {
|
||||||
|
return localDateTime.atZone(zoneId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ZonedDateTime toZonedDateTime(LocalDate day, LocalTime time) {
|
||||||
|
return toZonedDateTime(LocalDateTime.of(day, time));
|
||||||
|
}
|
||||||
|
|
||||||
|
private LocalDate toLocalDate(Instant instant) {
|
||||||
|
return LocalDate.ofInstant(instant, zoneId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ZonedDateTime max(ZonedDateTime a, ZonedDateTime b) {
|
||||||
if (a.isAfter(b)) {
|
if (a.isAfter(b)) {
|
||||||
return a;
|
return a;
|
||||||
} else {
|
} else {
|
||||||
|
@ -209,7 +232,7 @@ public class WorkingTimeCalculatorImpl implements WorkingTimeCalculator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Instant min(Instant a, Instant b) {
|
private static ZonedDateTime min(ZonedDateTime a, ZonedDateTime b) {
|
||||||
if (a.isBefore(b)) {
|
if (a.isBefore(b)) {
|
||||||
return a;
|
return a;
|
||||||
} else {
|
} else {
|
||||||
|
@ -219,21 +242,34 @@ public class WorkingTimeCalculatorImpl implements WorkingTimeCalculator {
|
||||||
|
|
||||||
class WorkSlot {
|
class WorkSlot {
|
||||||
|
|
||||||
private final Instant start;
|
private final ZonedDateTime start;
|
||||||
private final Instant end;
|
private final ZonedDateTime end;
|
||||||
|
|
||||||
public WorkSlot(LocalDate day, LocalTimeInterval interval) {
|
public WorkSlot(LocalDate day, LocalTimeInterval interval) {
|
||||||
this.start = LocalDateTime.of(day, interval.getBegin()).toInstant(UTC);
|
this.start = toZonedDateTime(day, interval.getBegin());
|
||||||
if (interval.getEnd().equals(LocalTime.MAX)) {
|
if (interval.getEnd().equals(LocalTime.MAX)) {
|
||||||
this.end = day.plusDays(1).atStartOfDay().toInstant(UTC);
|
this.end = toZonedDateTime(day.plusDays(1).atStartOfDay());
|
||||||
} else {
|
} else {
|
||||||
this.end = LocalDateTime.of(day, interval.getEnd()).toInstant(UTC);
|
this.end = toZonedDateTime(day, interval.getEnd());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Instant addWorkingTime(Instant workStart, Duration workingTime) {
|
private ZonedDateTime subtractWorkingTime(ZonedDateTime workStart, Duration workingTime) {
|
||||||
|
// _workStart_ might be outside the working hours. We need to adjust the end accordingly.
|
||||||
|
ZonedDateTime latestWorkEnd = min(workStart, end);
|
||||||
|
Duration untilStartOfWorkSlot = Duration.between(start, latestWorkEnd);
|
||||||
|
if (workingTime.compareTo(untilStartOfWorkSlot) <= 0) {
|
||||||
|
// easy part. It is due within the same work slot
|
||||||
|
return latestWorkEnd.minus(workingTime);
|
||||||
|
} else {
|
||||||
|
Duration remainingWorkingTime = workingTime.minus(untilStartOfWorkSlot);
|
||||||
|
return previous().subtractWorkingTime(start, remainingWorkingTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ZonedDateTime addWorkingTime(ZonedDateTime workStart, Duration workingTime) {
|
||||||
// _workStart_ might be outside the working hours. We need to adjust the start accordingly.
|
// _workStart_ might be outside the working hours. We need to adjust the start accordingly.
|
||||||
Instant earliestWorkStart = max(workStart, start);
|
ZonedDateTime earliestWorkStart = max(workStart, start);
|
||||||
Duration untilEndOfWorkSlot = Duration.between(earliestWorkStart, end);
|
Duration untilEndOfWorkSlot = Duration.between(earliestWorkStart, end);
|
||||||
if (workingTime.compareTo(untilEndOfWorkSlot) <= 0) {
|
if (workingTime.compareTo(untilEndOfWorkSlot) <= 0) {
|
||||||
// easy part. It is due within the same work slot
|
// easy part. It is due within the same work slot
|
||||||
|
@ -247,26 +283,13 @@ public class WorkingTimeCalculatorImpl implements WorkingTimeCalculator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Instant subtractWorkingTime(Instant workStart, Duration workingTime) {
|
|
||||||
// _workStart_ might be outside the working hours. We need to adjust the end accordingly.
|
|
||||||
Instant latestWorkEnd = min(workStart, end);
|
|
||||||
Duration untilStartOfWorkSlot = Duration.between(start, latestWorkEnd);
|
|
||||||
if (workingTime.compareTo(untilStartOfWorkSlot) <= 0) {
|
|
||||||
// easy part. It is due within the same work slot
|
|
||||||
return latestWorkEnd.minus(workingTime);
|
|
||||||
} else {
|
|
||||||
Duration remainingWorkingTime = workingTime.minus(untilStartOfWorkSlot);
|
|
||||||
return previous().subtractWorkingTime(start, remainingWorkingTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private WorkSlot previous() {
|
private WorkSlot previous() {
|
||||||
// We need to subtract a nanosecond because start is inclusive
|
// We need to subtract a nanosecond because start is inclusive
|
||||||
return getWorkSlotOrPrevious(toLocalDateTime(start.minusNanos(1)));
|
return getWorkSlotOrPrevious(start.minusNanos(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
private WorkSlot next() {
|
private WorkSlot next() {
|
||||||
return getWorkSlotOrNext(toLocalDateTime(end));
|
return getWorkSlotOrNext(end);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,11 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||||
import java.time.DayOfWeek;
|
import java.time.DayOfWeek;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.time.LocalTime;
|
import java.time.LocalTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
|
@ -33,7 +37,8 @@ class WorkingTimeCalculatorImplTest {
|
||||||
DayOfWeek.TUESDAY, standardWorkingSlots,
|
DayOfWeek.TUESDAY, standardWorkingSlots,
|
||||||
DayOfWeek.WEDNESDAY, standardWorkingSlots,
|
DayOfWeek.WEDNESDAY, standardWorkingSlots,
|
||||||
DayOfWeek.THURSDAY, standardWorkingSlots,
|
DayOfWeek.THURSDAY, standardWorkingSlots,
|
||||||
DayOfWeek.FRIDAY, standardWorkingSlots));
|
DayOfWeek.FRIDAY, standardWorkingSlots),
|
||||||
|
ZoneOffset.UTC);
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
class WorkingTimeAddition {
|
class WorkingTimeAddition {
|
||||||
|
@ -382,7 +387,8 @@ class WorkingTimeCalculatorImplTest {
|
||||||
DayOfWeek.TUESDAY, standardWorkday,
|
DayOfWeek.TUESDAY, standardWorkday,
|
||||||
DayOfWeek.WEDNESDAY, standardWorkday,
|
DayOfWeek.WEDNESDAY, standardWorkday,
|
||||||
DayOfWeek.THURSDAY, standardWorkday,
|
DayOfWeek.THURSDAY, standardWorkday,
|
||||||
DayOfWeek.FRIDAY, standardWorkday));
|
DayOfWeek.FRIDAY, standardWorkday),
|
||||||
|
ZoneOffset.UTC);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void addTimeToMatchEndOfFirstAndStartOfSecondSlot() {
|
void addTimeToMatchEndOfFirstAndStartOfSecondSlot() {
|
||||||
|
@ -417,7 +423,8 @@ class WorkingTimeCalculatorImplTest {
|
||||||
DayOfWeek.TUESDAY, completeWorkDay,
|
DayOfWeek.TUESDAY, completeWorkDay,
|
||||||
DayOfWeek.WEDNESDAY, completeWorkDay,
|
DayOfWeek.WEDNESDAY, completeWorkDay,
|
||||||
DayOfWeek.THURSDAY, completeWorkDay,
|
DayOfWeek.THURSDAY, completeWorkDay,
|
||||||
DayOfWeek.FRIDAY, completeWorkDay));
|
DayOfWeek.FRIDAY, completeWorkDay),
|
||||||
|
ZoneOffset.UTC);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void withDurationOfZeroOnHolySaturday() {
|
void withDurationOfZeroOnHolySaturday() {
|
||||||
|
@ -463,7 +470,8 @@ class WorkingTimeCalculatorImplTest {
|
||||||
private final WorkingTimeCalculator cut =
|
private final WorkingTimeCalculator cut =
|
||||||
new WorkingTimeCalculatorImpl(
|
new WorkingTimeCalculatorImpl(
|
||||||
new HolidaySchedule(true, false),
|
new HolidaySchedule(true, false),
|
||||||
Map.of(DayOfWeek.SUNDAY, Set.of(new LocalTimeInterval(LocalTime.MIN, LocalTime.MAX))));
|
Map.of(DayOfWeek.SUNDAY, Set.of(new LocalTimeInterval(LocalTime.MIN, LocalTime.MAX))),
|
||||||
|
ZoneOffset.UTC);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void returnsTrueIfWorkingTimeScheduleIsDefinedForDayOfWeek() {
|
void returnsTrueIfWorkingTimeScheduleIsDefinedForDayOfWeek() {
|
||||||
|
@ -498,4 +506,102 @@ class WorkingTimeCalculatorImplTest {
|
||||||
.isThrownBy(() -> cut.isWorkingDay(null));
|
.isThrownBy(() -> cut.isWorkingDay(null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class WorkingTimeWithNonUtcTimeZoneAcrossDaylightSavingTimeSwitch {
|
||||||
|
|
||||||
|
private final ZoneId cet = ZoneId.of("Europe/Berlin");
|
||||||
|
private final Set<LocalTimeInterval> completeWorkDay =
|
||||||
|
Set.of(new LocalTimeInterval(LocalTime.MIN, LocalTime.MAX));
|
||||||
|
|
||||||
|
private final WorkingTimeCalculator cut =
|
||||||
|
new WorkingTimeCalculatorImpl(
|
||||||
|
new HolidaySchedule(true, false),
|
||||||
|
Map.of(
|
||||||
|
DayOfWeek.MONDAY, completeWorkDay,
|
||||||
|
DayOfWeek.TUESDAY, completeWorkDay,
|
||||||
|
DayOfWeek.WEDNESDAY, completeWorkDay,
|
||||||
|
DayOfWeek.THURSDAY, completeWorkDay,
|
||||||
|
DayOfWeek.FRIDAY, completeWorkDay),
|
||||||
|
cet);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void addsWorkingTimeCorrectly() {
|
||||||
|
Instant fridayBefore =
|
||||||
|
ZonedDateTime.of(LocalDateTime.parse("2022-03-25T09:30:00"), cet).toInstant();
|
||||||
|
|
||||||
|
Instant dueDate = cut.addWorkingTime(fridayBefore, Duration.ofHours(17));
|
||||||
|
|
||||||
|
assertThat(dueDate)
|
||||||
|
.isEqualTo(ZonedDateTime.of(LocalDateTime.parse("2022-03-28T02:30:00"), cet).toInstant());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void subtractsWorkingTimeCorrectly() {
|
||||||
|
Instant mondayAfter =
|
||||||
|
ZonedDateTime.of(LocalDateTime.parse("2022-10-31T08:54:00"), cet).toInstant();
|
||||||
|
|
||||||
|
Instant dueDate = cut.subtractWorkingTime(mondayAfter, Duration.ofHours(18));
|
||||||
|
|
||||||
|
assertThat(dueDate)
|
||||||
|
.isEqualTo(ZonedDateTime.of(LocalDateTime.parse("2022-10-28T14:54:00"), cet).toInstant());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void calculatesWorkingTimeBetweenCorrectly() {
|
||||||
|
Instant fridayBefore =
|
||||||
|
ZonedDateTime.of(LocalDateTime.parse("2023-03-24T09:30:00"), cet).toInstant();
|
||||||
|
Instant mondayAfter =
|
||||||
|
ZonedDateTime.of(LocalDateTime.parse("2023-03-27T02:30:00"), cet).toInstant();
|
||||||
|
|
||||||
|
Duration duration = cut.workingTimeBetween(fridayBefore, mondayAfter);
|
||||||
|
|
||||||
|
assertThat(duration).isEqualTo(Duration.ofHours(17));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class WorkingTimeWithWorkSlotsSpanningAcrossDaylightSavingTimeSwitch {
|
||||||
|
|
||||||
|
ZoneId cet = ZoneId.of("Europe/Berlin");
|
||||||
|
WorkingTimeCalculator cut =
|
||||||
|
new WorkingTimeCalculatorImpl(
|
||||||
|
new HolidaySchedule(true, false),
|
||||||
|
Map.of(DayOfWeek.SUNDAY, Set.of(new LocalTimeInterval(LocalTime.MIN, LocalTime.MAX))),
|
||||||
|
cet);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void addsCorrectly() {
|
||||||
|
Instant beforeSwitch =
|
||||||
|
ZonedDateTime.of(LocalDateTime.parse("2023-03-26T00:00:00"), cet).toInstant();
|
||||||
|
|
||||||
|
Instant afterSwitch = cut.addWorkingTime(beforeSwitch, Duration.ofHours(3));
|
||||||
|
|
||||||
|
assertThat(afterSwitch)
|
||||||
|
.isEqualTo(ZonedDateTime.of(LocalDateTime.parse("2023-03-26T04:00:00"), cet).toInstant());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void subtractsCorrectly() {
|
||||||
|
Instant afterSwitch =
|
||||||
|
ZonedDateTime.of(LocalDateTime.parse("2023-03-26T05:00:00"), cet).toInstant();
|
||||||
|
|
||||||
|
Instant beforeSwitch = cut.subtractWorkingTime(afterSwitch, Duration.ofHours(3));
|
||||||
|
|
||||||
|
assertThat(beforeSwitch)
|
||||||
|
.isEqualTo(ZonedDateTime.of(LocalDateTime.parse("2023-03-26T01:00:00"), cet).toInstant());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void calculatesWorkingTimeBetweenCorrectly() {
|
||||||
|
Instant beforeSwitch =
|
||||||
|
ZonedDateTime.of(LocalDateTime.parse("2023-03-26T00:00:00"), cet).toInstant();
|
||||||
|
Instant afterSwitch =
|
||||||
|
ZonedDateTime.of(LocalDateTime.parse("2023-03-26T09:00:00"), cet).toInstant();
|
||||||
|
|
||||||
|
Duration duration = cut.workingTimeBetween(beforeSwitch, afterSwitch);
|
||||||
|
|
||||||
|
assertThat(duration).isEqualTo(Duration.ofHours(8));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ taskana.workingtime.schedule.TUESDAY=00:00-00:00
|
||||||
taskana.workingtime.schedule.WEDNESDAY=00:00-00:00
|
taskana.workingtime.schedule.WEDNESDAY=00:00-00:00
|
||||||
taskana.workingtime.schedule.THURSDAY=00:00-00:00
|
taskana.workingtime.schedule.THURSDAY=00:00-00:00
|
||||||
taskana.workingtime.schedule.FRIDAY=00:00-00:00
|
taskana.workingtime.schedule.FRIDAY=00:00-00:00
|
||||||
|
taskana.workingtime.timezone=UTC
|
||||||
# enable or disable the jobscheduler at all
|
# enable or disable the jobscheduler at all
|
||||||
# set it to false and no jobs are running
|
# set it to false and no jobs are running
|
||||||
taskana.jobscheduler.enabled=false
|
taskana.jobscheduler.enabled=false
|
||||||
|
@ -40,4 +41,3 @@ taskana.jobscheduler.enableTaskUpdatePriorityJob=true
|
||||||
taskana.jobscheduler.enableWorkbasketCleanupJob=true
|
taskana.jobscheduler.enableWorkbasketCleanupJob=true
|
||||||
taskana.jobscheduler.enableUserInfoRefreshJob=false
|
taskana.jobscheduler.enableUserInfoRefreshJob=false
|
||||||
taskana.jobscheduler.enableHistorieCleanupJob=true
|
taskana.jobscheduler.enableHistorieCleanupJob=true
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,8 @@ import java.time.DayOfWeek;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.LocalTime;
|
import java.time.LocalTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -114,6 +116,7 @@ class TaskanaConfigurationTest {
|
||||||
long expectedJobSchedulerPeriod = 10;
|
long expectedJobSchedulerPeriod = 10;
|
||||||
TimeUnit expectedJobSchedulerPeriodTimeUnit = TimeUnit.DAYS;
|
TimeUnit expectedJobSchedulerPeriodTimeUnit = TimeUnit.DAYS;
|
||||||
List<String> expectedJobSchedulerCustomJobs = List.of("Job_A", "Job_B");
|
List<String> expectedJobSchedulerCustomJobs = List.of("Job_A", "Job_B");
|
||||||
|
ZoneId expectedWorkingTimeScheduleTimeZone = ZoneId.ofOffset("UTC", ZoneOffset.ofHours(4));
|
||||||
|
|
||||||
// when
|
// when
|
||||||
Map<DayOfWeek, Set<LocalTimeInterval>> expectedWorkingTimeSchedule =
|
Map<DayOfWeek, Set<LocalTimeInterval>> expectedWorkingTimeSchedule =
|
||||||
|
@ -153,6 +156,7 @@ class TaskanaConfigurationTest {
|
||||||
.jobSchedulerEnableHistorieCleanupJob(false)
|
.jobSchedulerEnableHistorieCleanupJob(false)
|
||||||
.jobSchedulerCustomJobs(expectedJobSchedulerCustomJobs)
|
.jobSchedulerCustomJobs(expectedJobSchedulerCustomJobs)
|
||||||
.workingTimeSchedule(expectedWorkingTimeSchedule)
|
.workingTimeSchedule(expectedWorkingTimeSchedule)
|
||||||
|
.workingTimeScheduleTimeZone(expectedWorkingTimeScheduleTimeZone)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// then
|
// then
|
||||||
|
@ -194,6 +198,8 @@ class TaskanaConfigurationTest {
|
||||||
assertThat(configuration.isJobSchedulerEnableHistorieCleanupJob()).isFalse();
|
assertThat(configuration.isJobSchedulerEnableHistorieCleanupJob()).isFalse();
|
||||||
assertThat(configuration.getJobSchedulerCustomJobs()).isEqualTo(expectedJobSchedulerCustomJobs);
|
assertThat(configuration.getJobSchedulerCustomJobs()).isEqualTo(expectedJobSchedulerCustomJobs);
|
||||||
assertThat(configuration.getWorkingTimeSchedule()).isEqualTo(expectedWorkingTimeSchedule);
|
assertThat(configuration.getWorkingTimeSchedule()).isEqualTo(expectedWorkingTimeSchedule);
|
||||||
|
assertThat(configuration.getWorkingTimeScheduleTimeZone())
|
||||||
|
.isEqualTo(expectedWorkingTimeScheduleTimeZone);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -236,6 +242,7 @@ class TaskanaConfigurationTest {
|
||||||
.workingTimeSchedule(
|
.workingTimeSchedule(
|
||||||
Map.of(
|
Map.of(
|
||||||
DayOfWeek.MONDAY, Set.of(new LocalTimeInterval(LocalTime.MIN, LocalTime.NOON))))
|
DayOfWeek.MONDAY, Set.of(new LocalTimeInterval(LocalTime.MIN, LocalTime.NOON))))
|
||||||
|
.workingTimeScheduleTimeZone(ZoneId.ofOffset("UTC", ZoneOffset.ofHours(4)))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
TaskanaConfiguration copyConfiguration = new Builder(configuration).build();
|
TaskanaConfiguration copyConfiguration = new Builder(configuration).build();
|
||||||
|
|
|
@ -12,6 +12,7 @@ import java.time.DayOfWeek;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.LocalTime;
|
import java.time.LocalTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
|
@ -75,6 +76,8 @@ public class TaskanaConfiguration {
|
||||||
private final boolean corpusChristiEnabled;
|
private final boolean corpusChristiEnabled;
|
||||||
|
|
||||||
private final Map<DayOfWeek, Set<LocalTimeInterval>> workingTimeSchedule;
|
private final Map<DayOfWeek, Set<LocalTimeInterval>> workingTimeSchedule;
|
||||||
|
|
||||||
|
private final ZoneId workingTimeScheduleTimeZone;
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region history configuration
|
// region history configuration
|
||||||
|
@ -165,6 +168,7 @@ public class TaskanaConfiguration {
|
||||||
.collect(
|
.collect(
|
||||||
Collectors.toUnmodifiableMap(
|
Collectors.toUnmodifiableMap(
|
||||||
Entry::getKey, e -> Collections.unmodifiableSet(e.getValue())));
|
Entry::getKey, e -> Collections.unmodifiableSet(e.getValue())));
|
||||||
|
this.workingTimeScheduleTimeZone = builder.workingTimeScheduleTimeZone;
|
||||||
this.jobBatchSize = builder.jobBatchSize;
|
this.jobBatchSize = builder.jobBatchSize;
|
||||||
this.maxNumberOfJobRetries = builder.maxNumberOfJobRetries;
|
this.maxNumberOfJobRetries = builder.maxNumberOfJobRetries;
|
||||||
this.cleanupJobFirstRun = builder.cleanupJobFirstRun;
|
this.cleanupJobFirstRun = builder.cleanupJobFirstRun;
|
||||||
|
@ -249,6 +253,10 @@ public class TaskanaConfiguration {
|
||||||
return workingTimeSchedule;
|
return workingTimeSchedule;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ZoneId getWorkingTimeScheduleTimeZone() {
|
||||||
|
return workingTimeScheduleTimeZone;
|
||||||
|
}
|
||||||
|
|
||||||
public Map<TaskanaRole, Set<String>> getRoleMap() {
|
public Map<TaskanaRole, Set<String>> getRoleMap() {
|
||||||
return roleMap;
|
return roleMap;
|
||||||
}
|
}
|
||||||
|
@ -507,6 +515,9 @@ public class TaskanaConfiguration {
|
||||||
@TaskanaProperty("taskana.workingtime.schedule")
|
@TaskanaProperty("taskana.workingtime.schedule")
|
||||||
private Map<DayOfWeek, Set<LocalTimeInterval>> workingTimeSchedule =
|
private Map<DayOfWeek, Set<LocalTimeInterval>> workingTimeSchedule =
|
||||||
initDefaultWorkingTimeSchedule();
|
initDefaultWorkingTimeSchedule();
|
||||||
|
|
||||||
|
@TaskanaProperty("taskana.workingtime.timezone")
|
||||||
|
private ZoneId workingTimeScheduleTimeZone = ZoneId.of("Europe/Berlin");
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region history configuration
|
// region history configuration
|
||||||
|
@ -610,6 +621,7 @@ public class TaskanaConfiguration {
|
||||||
this.germanPublicHolidaysEnabled = conf.isGermanPublicHolidaysEnabled();
|
this.germanPublicHolidaysEnabled = conf.isGermanPublicHolidaysEnabled();
|
||||||
this.corpusChristiEnabled = conf.isCorpusChristiEnabled();
|
this.corpusChristiEnabled = conf.isCorpusChristiEnabled();
|
||||||
this.workingTimeSchedule = conf.getWorkingTimeSchedule();
|
this.workingTimeSchedule = conf.getWorkingTimeSchedule();
|
||||||
|
this.workingTimeScheduleTimeZone = conf.getWorkingTimeScheduleTimeZone();
|
||||||
this.jobBatchSize = conf.getJobBatchSize();
|
this.jobBatchSize = conf.getJobBatchSize();
|
||||||
this.maxNumberOfJobRetries = conf.getMaxNumberOfJobRetries();
|
this.maxNumberOfJobRetries = conf.getMaxNumberOfJobRetries();
|
||||||
this.cleanupJobFirstRun = conf.getCleanupJobFirstRun();
|
this.cleanupJobFirstRun = conf.getCleanupJobFirstRun();
|
||||||
|
@ -656,93 +668,78 @@ public class TaskanaConfiguration {
|
||||||
|
|
||||||
// region builder methods
|
// region builder methods
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
// TODO: why do we need this method?
|
// TODO: why do we need this method?
|
||||||
public Builder schemaName(String schemaName) {
|
public Builder schemaName(String schemaName) {
|
||||||
this.schemaName = initSchemaName(schemaName);
|
this.schemaName = initSchemaName(schemaName);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public Builder roleMap(Map<TaskanaRole, Set<String>> roleMap) {
|
public Builder roleMap(Map<TaskanaRole, Set<String>> roleMap) {
|
||||||
this.roleMap = roleMap;
|
this.roleMap = roleMap;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public Builder domains(List<String> domains) {
|
public Builder domains(List<String> domains) {
|
||||||
this.domains = domains;
|
this.domains = domains;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public Builder classificationTypes(List<String> classificationTypes) {
|
public Builder classificationTypes(List<String> classificationTypes) {
|
||||||
this.classificationTypes = classificationTypes;
|
this.classificationTypes = classificationTypes;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public Builder classificationCategoriesByTypeMap(
|
public Builder classificationCategoriesByTypeMap(
|
||||||
Map<String, List<String>> classificationCategoriesByTypeMap) {
|
Map<String, List<String>> classificationCategoriesByTypeMap) {
|
||||||
this.classificationCategoriesByType = classificationCategoriesByTypeMap;
|
this.classificationCategoriesByType = classificationCategoriesByTypeMap;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public Builder customHolidays(List<CustomHoliday> customHolidays) {
|
public Builder customHolidays(List<CustomHoliday> customHolidays) {
|
||||||
this.customHolidays = customHolidays;
|
this.customHolidays = customHolidays;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public Builder deleteHistoryOnTaskDeletionEnabled(boolean deleteHistoryOnTaskDeletionEnabled) {
|
public Builder deleteHistoryOnTaskDeletionEnabled(boolean deleteHistoryOnTaskDeletionEnabled) {
|
||||||
this.deleteHistoryOnTaskDeletionEnabled = deleteHistoryOnTaskDeletionEnabled;
|
this.deleteHistoryOnTaskDeletionEnabled = deleteHistoryOnTaskDeletionEnabled;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public Builder germanPublicHolidaysEnabled(boolean germanPublicHolidaysEnabled) {
|
public Builder germanPublicHolidaysEnabled(boolean germanPublicHolidaysEnabled) {
|
||||||
this.germanPublicHolidaysEnabled = germanPublicHolidaysEnabled;
|
this.germanPublicHolidaysEnabled = germanPublicHolidaysEnabled;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public Builder corpusChristiEnabled(boolean corpusChristiEnabled) {
|
public Builder corpusChristiEnabled(boolean corpusChristiEnabled) {
|
||||||
this.corpusChristiEnabled = corpusChristiEnabled;
|
this.corpusChristiEnabled = corpusChristiEnabled;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public Builder jobBatchSize(int jobBatchSize) {
|
public Builder jobBatchSize(int jobBatchSize) {
|
||||||
this.jobBatchSize = jobBatchSize;
|
this.jobBatchSize = jobBatchSize;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public Builder maxNumberOfJobRetries(int maxNumberOfJobRetries) {
|
public Builder maxNumberOfJobRetries(int maxNumberOfJobRetries) {
|
||||||
this.maxNumberOfJobRetries = maxNumberOfJobRetries;
|
this.maxNumberOfJobRetries = maxNumberOfJobRetries;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public Builder cleanupJobFirstRun(Instant cleanupJobFirstRun) {
|
public Builder cleanupJobFirstRun(Instant cleanupJobFirstRun) {
|
||||||
this.cleanupJobFirstRun = cleanupJobFirstRun;
|
this.cleanupJobFirstRun = cleanupJobFirstRun;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public Builder cleanupJobRunEvery(Duration cleanupJobRunEvery) {
|
public Builder cleanupJobRunEvery(Duration cleanupJobRunEvery) {
|
||||||
this.cleanupJobRunEvery = cleanupJobRunEvery;
|
this.cleanupJobRunEvery = cleanupJobRunEvery;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public Builder cleanupJobMinimumAge(Duration cleanupJobMinimumAge) {
|
public Builder cleanupJobMinimumAge(Duration cleanupJobMinimumAge) {
|
||||||
this.cleanupJobMinimumAge = cleanupJobMinimumAge;
|
this.cleanupJobMinimumAge = cleanupJobMinimumAge;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public Builder taskCleanupJobAllCompletedSameParentBusiness(
|
public Builder taskCleanupJobAllCompletedSameParentBusiness(
|
||||||
boolean taskCleanupJobAllCompletedSameParentBusiness) {
|
boolean taskCleanupJobAllCompletedSameParentBusiness) {
|
||||||
this.taskCleanupJobAllCompletedSameParentBusiness =
|
this.taskCleanupJobAllCompletedSameParentBusiness =
|
||||||
|
@ -750,56 +747,47 @@ public class TaskanaConfiguration {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public Builder allowTimestampServiceLevelMismatch(
|
public Builder allowTimestampServiceLevelMismatch(
|
||||||
boolean validationAllowTimestampServiceLevelMismatch) {
|
boolean validationAllowTimestampServiceLevelMismatch) {
|
||||||
this.allowTimestampServiceLevelMismatch = validationAllowTimestampServiceLevelMismatch;
|
this.allowTimestampServiceLevelMismatch = validationAllowTimestampServiceLevelMismatch;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public Builder addAdditionalUserInfo(boolean addAdditionalUserInfo) {
|
public Builder addAdditionalUserInfo(boolean addAdditionalUserInfo) {
|
||||||
this.addAdditionalUserInfo = addAdditionalUserInfo;
|
this.addAdditionalUserInfo = addAdditionalUserInfo;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public Builder priorityJobBatchSize(int priorityJobBatchSize) {
|
public Builder priorityJobBatchSize(int priorityJobBatchSize) {
|
||||||
this.priorityJobBatchSize = priorityJobBatchSize;
|
this.priorityJobBatchSize = priorityJobBatchSize;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public Builder priorityJobFirstRun(Instant priorityJobFirstRun) {
|
public Builder priorityJobFirstRun(Instant priorityJobFirstRun) {
|
||||||
this.priorityJobFirstRun = priorityJobFirstRun;
|
this.priorityJobFirstRun = priorityJobFirstRun;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public Builder priorityJobRunEvery(Duration priorityJobRunEvery) {
|
public Builder priorityJobRunEvery(Duration priorityJobRunEvery) {
|
||||||
this.priorityJobRunEvery = priorityJobRunEvery;
|
this.priorityJobRunEvery = priorityJobRunEvery;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public Builder priorityJobActive(boolean priorityJobActive) {
|
public Builder priorityJobActive(boolean priorityJobActive) {
|
||||||
this.priorityJobActive = priorityJobActive;
|
this.priorityJobActive = priorityJobActive;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public Builder userRefreshJobRunEvery(Duration userRefreshJobRunEvery) {
|
public Builder userRefreshJobRunEvery(Duration userRefreshJobRunEvery) {
|
||||||
this.userRefreshJobRunEvery = userRefreshJobRunEvery;
|
this.userRefreshJobRunEvery = userRefreshJobRunEvery;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public Builder userRefreshJobFirstRun(Instant userRefreshJobFirstRun) {
|
public Builder userRefreshJobFirstRun(Instant userRefreshJobFirstRun) {
|
||||||
this.userRefreshJobFirstRun = userRefreshJobFirstRun;
|
this.userRefreshJobFirstRun = userRefreshJobFirstRun;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public Builder minimalPermissionsToAssignDomains(
|
public Builder minimalPermissionsToAssignDomains(
|
||||||
List<WorkbasketPermission> minimalPermissionsToAssignDomains) {
|
List<WorkbasketPermission> minimalPermissionsToAssignDomains) {
|
||||||
this.minimalPermissionsToAssignDomains = minimalPermissionsToAssignDomains;
|
this.minimalPermissionsToAssignDomains = minimalPermissionsToAssignDomains;
|
||||||
|
@ -865,6 +853,11 @@ public class TaskanaConfiguration {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder workingTimeScheduleTimeZone(ZoneId workingTimeScheduleTimeZone) {
|
||||||
|
this.workingTimeScheduleTimeZone = workingTimeScheduleTimeZone;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public TaskanaConfiguration build() {
|
public TaskanaConfiguration build() {
|
||||||
validateConfiguration();
|
validateConfiguration();
|
||||||
return new TaskanaConfiguration(this);
|
return new TaskanaConfiguration(this);
|
||||||
|
@ -1018,17 +1011,13 @@ public class TaskanaConfiguration {
|
||||||
private static Map<DayOfWeek, Set<LocalTimeInterval>> initDefaultWorkingTimeSchedule() {
|
private static Map<DayOfWeek, Set<LocalTimeInterval>> initDefaultWorkingTimeSchedule() {
|
||||||
Map<DayOfWeek, Set<LocalTimeInterval>> workingTime = new EnumMap<>(DayOfWeek.class);
|
Map<DayOfWeek, Set<LocalTimeInterval>> workingTime = new EnumMap<>(DayOfWeek.class);
|
||||||
|
|
||||||
// Default working time schedule is from Monday 00:00 - Friday 24:00, but CET (hence -1 hour)
|
|
||||||
Set<LocalTimeInterval> standardWorkingSlots =
|
Set<LocalTimeInterval> standardWorkingSlots =
|
||||||
Set.of(new LocalTimeInterval(LocalTime.MIN, LocalTime.MAX));
|
Set.of(new LocalTimeInterval(LocalTime.MIN, LocalTime.MAX));
|
||||||
workingTime.put(
|
|
||||||
DayOfWeek.SUNDAY, Set.of(new LocalTimeInterval(LocalTime.of(23, 0), LocalTime.MAX)));
|
|
||||||
workingTime.put(DayOfWeek.MONDAY, standardWorkingSlots);
|
workingTime.put(DayOfWeek.MONDAY, standardWorkingSlots);
|
||||||
workingTime.put(DayOfWeek.TUESDAY, standardWorkingSlots);
|
workingTime.put(DayOfWeek.TUESDAY, standardWorkingSlots);
|
||||||
workingTime.put(DayOfWeek.WEDNESDAY, standardWorkingSlots);
|
workingTime.put(DayOfWeek.WEDNESDAY, standardWorkingSlots);
|
||||||
workingTime.put(DayOfWeek.THURSDAY, standardWorkingSlots);
|
workingTime.put(DayOfWeek.THURSDAY, standardWorkingSlots);
|
||||||
workingTime.put(
|
workingTime.put(DayOfWeek.FRIDAY, standardWorkingSlots);
|
||||||
DayOfWeek.FRIDAY, Set.of(new LocalTimeInterval(LocalTime.MIN, LocalTime.of(23, 0))));
|
|
||||||
return workingTime;
|
return workingTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,7 +132,9 @@ public class TaskanaEngineImpl implements TaskanaEngine {
|
||||||
taskanaConfiguration.getCustomHolidays());
|
taskanaConfiguration.getCustomHolidays());
|
||||||
workingTimeCalculator =
|
workingTimeCalculator =
|
||||||
new WorkingTimeCalculatorImpl(
|
new WorkingTimeCalculatorImpl(
|
||||||
holidaySchedule, taskanaConfiguration.getWorkingTimeSchedule());
|
holidaySchedule,
|
||||||
|
taskanaConfiguration.getWorkingTimeSchedule(),
|
||||||
|
taskanaConfiguration.getWorkingTimeScheduleTimeZone());
|
||||||
currentUserContext =
|
currentUserContext =
|
||||||
new CurrentUserContextImpl(TaskanaConfiguration.shouldUseLowerCaseForAccessIds());
|
new CurrentUserContextImpl(TaskanaConfiguration.shouldUseLowerCaseForAccessIds());
|
||||||
createTransactionFactory(taskanaConfiguration.isUseManagedTransactions());
|
createTransactionFactory(taskanaConfiguration.isUseManagedTransactions());
|
||||||
|
|
|
@ -5,6 +5,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import java.time.DayOfWeek;
|
import java.time.DayOfWeek;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.LocalTime;
|
import java.time.LocalTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -40,7 +41,8 @@ class WorkingDaysToDaysReportConverterTest {
|
||||||
workingTimeCalculator =
|
workingTimeCalculator =
|
||||||
new WorkingTimeCalculatorImpl(
|
new WorkingTimeCalculatorImpl(
|
||||||
new HolidaySchedule(true, false, List.of(dayOfReformation, allSaintsDays)),
|
new HolidaySchedule(true, false, List.of(dayOfReformation, allSaintsDays)),
|
||||||
workingTime);
|
workingTime,
|
||||||
|
ZoneOffset.UTC);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -24,6 +24,7 @@ taskana.workingtime.schedule.TUESDAY=00:00-00:00
|
||||||
taskana.workingtime.schedule.WEDNESDAY=00:00-00:00
|
taskana.workingtime.schedule.WEDNESDAY=00:00-00:00
|
||||||
taskana.workingtime.schedule.THURSDAY=00:00-00:00
|
taskana.workingtime.schedule.THURSDAY=00:00-00:00
|
||||||
taskana.workingtime.schedule.FRIDAY=00:00-00:00
|
taskana.workingtime.schedule.FRIDAY=00:00-00:00
|
||||||
|
taskana.workingtime.timezone=UTC
|
||||||
# enable or disable the jobscheduler at all
|
# enable or disable the jobscheduler at all
|
||||||
# set it to false and no jobs are running
|
# set it to false and no jobs are running
|
||||||
taskana.jobscheduler.enabled=false
|
taskana.jobscheduler.enabled=false
|
||||||
|
|
Loading…
Reference in New Issue