Current LocalTime is inside of a TimeRange defined from a properties
package es.jaranda.commons.timeutils.application.rule;
import java.time.Instant;
import java.util.function.Predicate;
public interface CurrentLocalTimeIsInsideTimeRangeRule
extends Predicate<Instant> { }
package es.jaranda.commons.timeutils.application.rule.impl;
import es.jaranda.commons.timeutils.application.properties.TimeRangeContract;
import es.jaranda.commons.timeutils.application.rule.CurrentLocalTimeIsInsideTimeRangeRule;
import java.time.Instant;
import java.time.LocalTime;
import java.time.ZoneId;
public class CurrentLocalTimeIsInsideTimeRangeRuleImpl
implements CurrentLocalTimeIsInsideTimeRangeRule {
private final TimeRangeContract timeRangeContract;
public CurrentLocalTimeIsInsideTimeRangeRuleImpl(
final TimeRangeContract timeRangeContract) {
this.timeRangeContract = timeRangeContract;
}
@Override
public boolean test(final Instant currentInstant) {
final ZoneId zoneId = ZoneId.of(
timeRangeContract.getAppliedTimeZoneForTimeRange()
);
final Integer minHour = timeRangeContract.getStartHourOfTimeRange();
final Integer maxHour = timeRangeContract.getEndHourOfTimeRange();
final LocalTime minLocalTime = LocalTime.of(minHour, 0);
final LocalTime maxLocalTime = LocalTime.of(
maxHour, 59, 59, 999_999_999
);
final LocalTime currentLocalTime =
currentInstant.atZone(zoneId).toLocalTime();
final boolean sameDayInterval =
isSameDayInterval(minLocalTime, maxLocalTime, currentLocalTime);
final boolean betweenDaysInterval = isBetweenDaysInterval(
minLocalTime, maxLocalTime, currentLocalTime
);
return sameDayInterval || betweenDaysInterval;
}
private boolean isBetweenDaysInterval(final LocalTime minLocalTime,
final LocalTime maxLocalTime,
final LocalTime currentLocalTime) {
return minLocalTime.isAfter(maxLocalTime) &&
(!currentLocalTime.isAfter(minLocalTime) ||
!currentLocalTime.isBefore(maxLocalTime));
}
private boolean isSameDayInterval(final LocalTime minLocalTime,
final LocalTime maxLocalTime,
final LocalTime currentLocalTime) {
return !minLocalTime.isAfter(maxLocalTime) &&
!currentLocalTime.isAfter(maxLocalTime) &&
!currentLocalTime.isBefore(minLocalTime);
}
}
package es.jaranda.commons.timeutils.application.rule
import es.jaranda.commons.timeutils.application.properties.TimeRangeContract
import es.jaranda.commons.timeutils.application.rule.impl.CurrentLocalTimeIsInsideTimeRangeRuleImpl
import spock.lang.Specification
import java.time.Instant
import java.time.OffsetDateTime
import java.time.zone.ZoneRulesException
class CurrentLocalTimeIsInsideTimeRangeRuleSpec extends Specification {
private static final String UTC_TIME_ZONE_CODE = "UTC"
private static final String BAD_TIME_ZONE_CODE = "badTimeZone"
private static final String VALID_ISO_8601_IN_UTC_ZONE =
"2016-04-15T10:00:00Z"
private static final String VALID_ISO_8601_IN_UTC_ZONE_MAX_LIMIT =
"2016-04-15T10:59:59.999Z"
private static final String VALID_ISO_8601_DAYLIGHT_SAVING_TIME_DAY =
"2018-03-25T02:30:00+01:00"
private static final String MADRID_TIME_ZONE_CODE = "Europe/Madrid"
private final def timeRangeContractMock = Mock(TimeRangeContract)
private final def currentLocalTimeIsInsideTimeRangeRule =
new CurrentLocalTimeIsInsideTimeRangeRuleImpl(timeRangeContractMock)
def "Should be is inside of range when is same day and is between"() {
given:
final def currentInstant = Instant.parse(VALID_ISO_8601_IN_UTC_ZONE)
1 * timeRangeContractMock.startHourOfTimeRange >> 8
1 * timeRangeContractMock.endHourOfTimeRange >> 11
1 * timeRangeContractMock.appliedTimeZoneForTimeRange >>
UTC_TIME_ZONE_CODE
when:
def isInsideRange =
currentLocalTimeIsInsideTimeRangeRule.test(currentInstant)
then:
isInsideRange
}
def "Should be is inside of range when is same day and is not between"() {
given:
final def currentInstant = Instant.parse(VALID_ISO_8601_IN_UTC_ZONE)
1 * timeRangeContractMock.startHourOfTimeRange >> 11
1 * timeRangeContractMock.endHourOfTimeRange >> 13
1 * timeRangeContractMock.appliedTimeZoneForTimeRange >>
UTC_TIME_ZONE_CODE
when:
def isInsideRange =
currentLocalTimeIsInsideTimeRangeRule.test(currentInstant)
then:
!isInsideRange
}
def "Should be is inside of range when is same day and is min-limit"() {
given:
final def currentInstant = Instant.parse(VALID_ISO_8601_IN_UTC_ZONE)
1 * timeRangeContractMock.startHourOfTimeRange >> 10
1 * timeRangeContractMock.endHourOfTimeRange >> 15
1 * timeRangeContractMock.appliedTimeZoneForTimeRange >>
UTC_TIME_ZONE_CODE
when:
def isInsideRange =
currentLocalTimeIsInsideTimeRangeRule.test(currentInstant)
then:
isInsideRange
}
def """Should be is inside of range when is same day and is max hour limit
"""() {
given:
final def currentInstant = Instant.parse(VALID_ISO_8601_IN_UTC_ZONE)
1 * timeRangeContractMock.startHourOfTimeRange >> 8
1 * timeRangeContractMock.endHourOfTimeRange >> 10
1 * timeRangeContractMock.appliedTimeZoneForTimeRange >>
UTC_TIME_ZONE_CODE
when:
def isInsideRange =
currentLocalTimeIsInsideTimeRangeRule.test(currentInstant)
then:
isInsideRange
}
def "Should be is inside of range when is same day and is max-limit"() {
given:
final def currentInstant = Instant.parse(
VALID_ISO_8601_IN_UTC_ZONE_MAX_LIMIT
)
1 * timeRangeContractMock.startHourOfTimeRange >> 8
1 * timeRangeContractMock.endHourOfTimeRange >> 10
1 * timeRangeContractMock.appliedTimeZoneForTimeRange >>
UTC_TIME_ZONE_CODE
when:
def isInsideRange =
currentLocalTimeIsInsideTimeRangeRule.test(currentInstant)
then:
isInsideRange
}
def """Should be is inside of range when is same day and is
min and max hour limit"""() {
given:
final def currentInstant = Instant.parse(VALID_ISO_8601_IN_UTC_ZONE)
1 * timeRangeContractMock.startHourOfTimeRange >> 10
1 * timeRangeContractMock.endHourOfTimeRange >> 10
1 * timeRangeContractMock.appliedTimeZoneForTimeRange >>
UTC_TIME_ZONE_CODE
when:
def isInsideRange =
currentLocalTimeIsInsideTimeRangeRule.test(currentInstant)
then:
isInsideRange
}
def """Should be is inside of range when is same day and is
min hour and max limit"""() {
given:
final def currentInstant = Instant.parse(
VALID_ISO_8601_IN_UTC_ZONE_MAX_LIMIT
)
1 * timeRangeContractMock.startHourOfTimeRange >> 10
1 * timeRangeContractMock.endHourOfTimeRange >> 10
1 * timeRangeContractMock.appliedTimeZoneForTimeRange >>
UTC_TIME_ZONE_CODE
when:
def isInsideRange =
currentLocalTimeIsInsideTimeRangeRule.test(currentInstant)
then:
isInsideRange
}
def """Should be is inside of range when is between two days and is
between"""() {
given:
final def currentInstant = Instant.parse(VALID_ISO_8601_IN_UTC_ZONE)
1 * timeRangeContractMock.startHourOfTimeRange >> 22
1 * timeRangeContractMock.endHourOfTimeRange >> 12
1 * timeRangeContractMock.appliedTimeZoneForTimeRange >>
UTC_TIME_ZONE_CODE
when:
def isInsideRange =
currentLocalTimeIsInsideTimeRangeRule.test(currentInstant)
then:
isInsideRange
}
def "Should be outside when is between two days and is not between"() {
given:
final def currentInstant = Instant.parse(VALID_ISO_8601_IN_UTC_ZONE)
1 * timeRangeContractMock.startHourOfTimeRange >> 11
1 * timeRangeContractMock.endHourOfTimeRange >> 15
1 * timeRangeContractMock.appliedTimeZoneForTimeRange >>
UTC_TIME_ZONE_CODE
when:
def isInsideRange =
currentLocalTimeIsInsideTimeRangeRule.test(currentInstant)
then:
!isInsideRange
}
def "Should be outside when is daylight saving and is missing hour of day"() {
given:
final def currentInstant = OffsetDateTime.
parse(VALID_ISO_8601_DAYLIGHT_SAVING_TIME_DAY).toInstant()
1 * timeRangeContractMock.startHourOfTimeRange >> 2
1 * timeRangeContractMock.endHourOfTimeRange >> 2
1 * timeRangeContractMock.appliedTimeZoneForTimeRange >>
MADRID_TIME_ZONE_CODE
when:
def isInsideRange =
currentLocalTimeIsInsideTimeRangeRule.test(currentInstant)
then:
!isInsideRange
}
def "Should fail when bad time zone is specified"() {
given:
final def currentInstant = Instant.parse(VALID_ISO_8601_IN_UTC_ZONE)
timeRangeContractMock.startHourOfTimeRange >> 8
timeRangeContractMock.endHourOfTimeRange >> 11
1 * timeRangeContractMock.appliedTimeZoneForTimeRange >>
BAD_TIME_ZONE_CODE
when:
currentLocalTimeIsInsideTimeRangeRule.test(currentInstant)
then:
final ZoneRulesException ex = thrown()
ex
}
}
@file:JvmName("LocalTimeRangeUtils")
package es.jaranda.commons.timeutils.utils
import es.jaranda.commons.timeutils.application.properties.TimeRangeContract
import es.jaranda.commons.timeutils.application.rule.impl.CurrentLocalTimeIsInsideTimeRangeRuleImpl
import java.time.*
fun currentInstantIsInsideTimeRange(zoneId : ZoneId,
startRangeHour : Int,
endRangeHour: Int) =
instantIsInsideHourTimeRange(
Instant.now(), zoneId, startRangeHour, endRangeHour
)
fun instantIsInsideHourTimeRange(instant : Instant,
zoneId: ZoneId,
startRangeHour : Int,
endRangeHour: Int) =
CurrentLocalTimeIsInsideTimeRangeRuleImpl(
object : TimeRangeContract {
override fun getStartHourOfTimeRange(): Int {
return startRangeHour
}
override fun getEndHourOfTimeRange(): Int {
return endRangeHour
}
override fun getAppliedTimeZoneForTimeRange(): String {
return zoneId.id
}
}
).test(instant)
fun Instant.isInsideHourTimeRange(zoneId : ZoneId,
startRangeHour: Int,
endRangeHour: Int) =
instantIsInsideHourTimeRange(this, zoneId, startRangeHour, endRangeHour)
fun OffsetDateTime.isInsideHourTimeRange(startRangeHour: Int,
endRangeHour: Int) =
instantIsInsideHourTimeRange(this.toInstant(), this.offset,
startRangeHour, endRangeHour)
fun ZonedDateTime.isInsideHourTimeRange(startRangeHour: Int,
endRangeHour: Int) =
instantIsInsideHourTimeRange(this.toInstant(), this.offset,
startRangeHour, endRangeHour)
fun LocalDateTime.isInsideHourTimeRange(startRangeHour: Int,
endRangeHour: Int) =
instantIsInsideHourTimeRange(
this.toInstant(ZoneOffset.UTC),
ZoneOffset.UTC, startRangeHour, endRangeHour
)
fun LocalTime.isInsideHourTimeRange(startRangeHour: Int,
endRangeHour: Int) =
instantIsInsideHourTimeRange(
this.atDate(
LocalDate.of(2018,1,1)
).toInstant(ZoneOffset.UTC),
ZoneOffset.UTC, startRangeHour, endRangeHour
)
package es.jaranda.commons.timeutils.infrastructure.properties;
import es.jaranda.commons.timeutils.application.properties.TimeRangeContract;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.annotation.Validated;
@Validated
@Configuration
@ConfigurationProperties(
prefix = "es.jaranda.commons.timeutils.examples.timerangeexample")
public class TimeRangeConfigurationProperties implements TimeRangeContract {
private Integer startHourOfTimeRange;
private Integer endHourOfTimeRange;
private String appliedTimeZoneOfTimeRange;
@Override
public Integer getStartHourOfTimeRange() {
return startHourOfTimeRange;
}
@Override
public Integer getEndHourOfTimeRange() {
return endHourOfTimeRange;
}
@Override
public String getAppliedTimeZoneForTimeRange() {
return appliedTimeZoneOfTimeRange;
}
public void setStartHourOfTimeRange(final Integer startHourOfTimeRange) {
this.startHourOfTimeRange = startHourOfTimeRange;
}
public void setEndHourOfTimeRange(final Integer endHourOfTimeRange) {
this.endHourOfTimeRange = endHourOfTimeRange;
}
public void setAppliedTimeZoneOfTimeRange(
final String appliedTimeZoneOfTimeRange) {
this.appliedTimeZoneOfTimeRange = appliedTimeZoneOfTimeRange;
}
}
package es.jaranda.commons.timeutils.application.properties;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
public interface TimeRangeContract {
@NotNull
@Min(0)
@Max(23)
Integer getStartHourOfTimeRange();
@NotNull
@Min(0)
@Max(23)
Integer getEndHourOfTimeRange();
@NotNull
String getAppliedTimeZoneForTimeRange();
}