Skip to content

Commit

Permalink
Merge pull request #844 from dsmf/feat/639-support-any-iso-date
Browse files Browse the repository at this point in the history
Feat/639 support any iso date
  • Loading branch information
ds-jhartmann authored Jul 23, 2024
2 parents a218a58 + 01fef00 commit 37f9b24
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 28 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ _**For better traceability add the corresponding GitHub issue number in each cha

### Changed

- The date search operators `AFTER_LOCAL_DATE` and `BEFORE_LOCAL_DATE` for fields `createdOn` and `validUntil` support any ISO date time now (relates to #639 and #750).
- Improved documentation for `GET /irs/policies/paged` endpoint. #639
- Cleanup in IrsApplicationTest.generatedOpenApiMatchesContract
(removed obsolete ignoringFields, improved assertion message)
Expand Down
13 changes: 7 additions & 6 deletions docs/src/api/irs-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1049,13 +1049,14 @@ paths:
```\n \n### Filtering\n \n`search=<Field>,[EQUALS|STARTS_WITH|BEFORE_LOCAL_DATE|AFTER_LOCAL_DATE],<Value>`.\n\
\ \nExample: `search=BPN,STARTS_WITH,BPNL12&search=policyId,STARTS_WITH,policy2`.\n\
\ \n| Field | Supported Operations | Value Format\
\ |\n|--------------|------------------------------------------|----------------------|\n\
\ |\n|--------------|------------------------------------------|------------------------------------|\n\
| `BPN` | `EQUALS`, `STARTS_WITH` | any string \
\ |\n| `policyId` | `EQUALS`, `STARTS_WITH` | any\
\ string |\n| `action` | `EQUALS` \
\ | `use` or `access` |\n| `createdOn` | `BEFORE_LOCAL_DATE`, `AFTER_LOCAL_DATE`\
\ | `yyyy-MM-dd` |\n| `validUntil` | `BEFORE_LOCAL_DATE`, `AFTER_LOCAL_DATE`\
\ | `yyyy-MM-dd` |\n\n### Sorting\n`sort=[BPN|policyId|action|createdOn|validUntil],[asc|desc]`.\n\
\ |\n| `policyId` | `EQUALS`, `STARTS_WITH` \
\ | any string |\n| `action` | `EQUALS`\
\ | `use` or `access` |\n\
| `createdOn` | `BEFORE_LOCAL_DATE`, `AFTER_LOCAL_DATE` | `yyyy-MM-dd` or\
\ ISO date with time |\n| `validUntil` | `BEFORE_LOCAL_DATE`, `AFTER_LOCAL_DATE`\
\ | `yyyy-MM-dd` or ISO date with time |\n\n### Sorting\n`sort=[BPN|policyId|action|createdOn|validUntil],[asc|desc]`.\n\
\ \nExample: `sort=BPN,asc&sort=policyId,desc`. _(default: `BPN,asc`)_\n\n\
### Paging\n \nExample: `page=1&size=20` _(default page size: 10, maximal\
\ page size: 1000)_\n"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,41 @@
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

/**
* Date utilities.
*/
@Slf4j
public final class DateUtils {

public static final String SIMPLE_DATE_WITHOUT_TIME = "yyyy-MM-dd";

private DateUtils() {
// private constructor (utility class)
}

public static boolean isDateBefore(final OffsetDateTime dateTime, final String referenceDateString) {
return dateTime.isBefore(toOffsetDateTimeAtStartOfDay(referenceDateString));
if (isDateWithoutTime(referenceDateString)) {
return dateTime.isBefore(toOffsetDateTimeAtStartOfDay(referenceDateString));
} else {
return dateTime.isBefore(OffsetDateTime.parse(referenceDateString));
}
}

public static boolean isDateAfter(final OffsetDateTime dateTime, final String referenceDateString) {
return dateTime.isAfter(toOffsetDateTimeAtEndOfDay(referenceDateString));
if (StringUtils.isBlank(referenceDateString)) {
throw new IllegalArgumentException("Invalid date: must not be blank!");
}
if (isDateWithoutTime(referenceDateString)) {
return dateTime.isAfter(toOffsetDateTimeAtEndOfDay(referenceDateString));
} else {
return dateTime.isAfter(OffsetDateTime.parse(referenceDateString));
}
}

public static OffsetDateTime toOffsetDateTimeAtStartOfDay(final String dateString) {
Expand All @@ -62,4 +78,31 @@ private static LocalDate parseDate(final String dateString) {
throw new IllegalArgumentException("Invalid date format (please refer to the documentation)", e);
}
}

@SuppressWarnings("PMD.PreserveStackTrace") // this is intended here as we try to parse with different formats
public static boolean isDateWithoutTime(final String referenceDateString) {
try {
DateTimeFormatter.ofPattern(SIMPLE_DATE_WITHOUT_TIME).parse(referenceDateString);
return true;
} catch (DateTimeParseException e) {
// ignore, trying next format below
log.trace(e.getMessage(), e);
}

try {
OffsetDateTime.parse(referenceDateString, DateTimeFormatter.ISO_DATE);
return true;
} catch (DateTimeParseException e) {
// ignore, trying next format below
log.trace(e.getMessage(), e);
}

try {
OffsetDateTime.parse(referenceDateString, DateTimeFormatter.ISO_DATE_TIME);
return false;
} catch (DateTimeParseException e) {
log.trace(e.getMessage(), e);
throw new IllegalArgumentException("Invalid date format: " + referenceDateString, e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -281,13 +281,13 @@ public List<String> autocomplete(
\s
Example: `search=BPN,STARTS_WITH,BPNL12&search=policyId,STARTS_WITH,policy2`.
\s
| Field | Supported Operations | Value Format |
|--------------|------------------------------------------|----------------------|
| `BPN` | `EQUALS`, `STARTS_WITH` | any string |
| `policyId` | `EQUALS`, `STARTS_WITH` | any string |
| `action` | `EQUALS` | `use` or `access` |
| `createdOn` | `BEFORE_LOCAL_DATE`, `AFTER_LOCAL_DATE` | `yyyy-MM-dd` |
| `validUntil` | `BEFORE_LOCAL_DATE`, `AFTER_LOCAL_DATE` | `yyyy-MM-dd` |
| Field | Supported Operations | Value Format |
|--------------|------------------------------------------|------------------------------------|
| `BPN` | `EQUALS`, `STARTS_WITH` | any string |
| `policyId` | `EQUALS`, `STARTS_WITH` | any string |
| `action` | `EQUALS` | `use` or `access` |
| `createdOn` | `BEFORE_LOCAL_DATE`, `AFTER_LOCAL_DATE` | `yyyy-MM-dd` or ISO date with time |
| `validUntil` | `BEFORE_LOCAL_DATE`, `AFTER_LOCAL_DATE` | `yyyy-MM-dd` or ISO date with time |
### Sorting
`sort=[BPN|policyId|action|createdOn|validUntil],[asc|desc]`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.util.stream.Stream;

import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
Expand All @@ -45,11 +46,25 @@ void testIsDateBefore(final OffsetDateTime dateTime, final String referenceDateS
}

static Stream<Arguments> provideDatesForIsDateBefore() {
final OffsetDateTime referenceDateTime = LocalDate.parse("2024-07-05").atStartOfDay().atOffset(ZoneOffset.UTC);
return Stream.of( //
Arguments.of(referenceDateTime, "2024-07-04", false),
Arguments.of(referenceDateTime, "2024-07-05", false),
Arguments.of(referenceDateTime, "2024-07-06", true));
Arguments.of(atStartOfDay("2024-07-05"), "2024-07-04", false), //
Arguments.of(atStartOfDay("2024-07-05"), "2024-07-05", false), //
Arguments.of(atStartOfDay("2024-07-05"), "2024-07-06", true), //
Arguments.of(OffsetDateTime.parse("2024-07-23T15:30:00Z"), "2024-07-23T15:30:00Z", false), //
Arguments.of(OffsetDateTime.parse("2024-07-23T15:30:00Z"), "2024-07-23T15:30:01Z", true), //
Arguments.of(OffsetDateTime.parse("2023-12-01T08:45:00+05:30"), "2023-12-01T08:45:00+05:30", false), //
Arguments.of(OffsetDateTime.parse("2023-12-01T08:45:00+05:30"), "2023-12-01T08:46:01+05:30", true), //
Arguments.of(OffsetDateTime.parse("2022-11-15T22:15:30-04:00"), "2022-11-15T22:15:30-04:00", false), //
Arguments.of(OffsetDateTime.parse("2022-11-15T22:15:30-04:00"), "2022-11-15T22:16:01-04:00", true), //
Arguments.of(OffsetDateTime.parse("2021-06-30T14:00:00.123Z"), "2021-06-30T14:00:00.123Z", false), //
Arguments.of(OffsetDateTime.parse("2021-06-30T14:00:00.123Z"), "2021-06-30T14:00:00.124Z", true), //
Arguments.of(OffsetDateTime.parse("2021-06-29T00:00Z"), "2021-06-29T00:00Z", false), //
Arguments.of(OffsetDateTime.parse("2021-06-29T00:00Z"), "2021-06-30T00:01Z", true) //
);
}

private static OffsetDateTime atStartOfDay(final String date) {
return LocalDate.parse(date).atStartOfDay().atOffset(ZoneOffset.UTC);
}

@ParameterizedTest
Expand All @@ -59,27 +74,57 @@ void testIsDateAfter(final OffsetDateTime dateTime, final String dateString, fin
}

static Stream<Arguments> provideDatesForIsDateAfter() {
final OffsetDateTime referenceDateTime = LocalDate.parse("2023-07-05")
.atTime(LocalTime.MAX)
.atOffset(ZoneOffset.UTC);

return Stream.of( //
Arguments.of(referenceDateTime, "2023-07-04", true),
Arguments.of(referenceDateTime, "2023-07-05", false),
Arguments.of(referenceDateTime, "2023-07-06", false));
Arguments.of(atStartOfDay("2024-07-05"), "2024-07-04", true), //
Arguments.of(atStartOfDay("2024-07-05"), "2024-07-05", false), //
Arguments.of(atStartOfDay("2024-07-05"), "2024-07-06", false), //
Arguments.of(OffsetDateTime.parse("2024-07-23T15:30:00Z"), "2024-07-23T15:30:00Z", false), //
Arguments.of(OffsetDateTime.parse("2024-07-23T15:30:00Z"), "2024-07-23T15:29:59Z", true), //
Arguments.of(OffsetDateTime.parse("2023-12-01T08:45:00+05:30"), "2023-12-01T08:45:00+05:30", false), //
Arguments.of(OffsetDateTime.parse("2023-12-01T08:45:00+05:30"), "2023-12-01T08:44:59+05:30", true), //
Arguments.of(OffsetDateTime.parse("2022-11-15T22:15:30-04:00"), "2022-11-15T22:15:30-04:00", false), //
Arguments.of(OffsetDateTime.parse("2022-11-15T22:15:30-04:00"), "2022-11-15T22:15:29-04:00", true), //
Arguments.of(OffsetDateTime.parse("2021-06-30T14:00:00.123Z"), "2021-06-30T14:00:00.123Z", false), //
Arguments.of(OffsetDateTime.parse("2021-06-30T14:00:00.123Z"), "2021-06-30T14:00:00.122Z", true), //
Arguments.of(OffsetDateTime.parse("2021-06-29T00:00Z"), "2021-06-29T00:00Z", false), //
Arguments.of(OffsetDateTime.parse("2021-06-29T00:00Z"), "2021-06-28T00:01Z", true) //
);
}

private static OffsetDateTime atEndOfDay(final String dateStr) {
return LocalDate.parse(dateStr).atTime(LocalTime.MAX).atOffset(ZoneOffset.UTC);
}

@ParameterizedTest
@MethodSource("provideDatesForIsDateWithoutTime")
public void isDateWithoutTime(final String dateString, final boolean expected) {
assertThat(DateUtils.isDateWithoutTime(dateString)).isEqualTo(expected);
}

static Stream<Arguments> provideDatesForIsDateWithoutTime() {
return Stream.of( //
Arguments.of("2023-07-23", true), //
Arguments.of("2023-07-23T10:15:30+01:00", false), //
Arguments.of("2023-07-23T10:15:30Z", false) //
);
}

@Test
public void testIsDateWithoutTimeWithInvalidDate() {
assertThatThrownBy(() -> DateUtils.isDateWithoutTime("invalid-date")).isInstanceOf(
IllegalArgumentException.class).hasMessageContaining("Invalid date format: invalid-date");
}

@ParameterizedTest
@ValueSource(strings = { "3333-11-11T11:11:11.111Z",
"3333-11-",
@ValueSource(strings = { "3333-11-",
"2222",
"asdf"
})
void testInvalidDate(final String referenceDateStr) {
final ThrowingCallable call = () -> DateUtils.isDateAfter(OffsetDateTime.now(), referenceDateStr);
assertThatThrownBy(call).isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Invalid date")
.hasMessageContaining("refer to the documentation")
.hasCauseInstanceOf(DateTimeParseException.class);
}

Expand Down

0 comments on commit 37f9b24

Please sign in to comment.