Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Create StartDate and EndDate types [DHIS2-16019] #17454

Merged
merged 16 commits into from
May 22, 2024
Merged
32 changes: 30 additions & 2 deletions dhis-2/dhis-api/src/main/java/org/hisp/dhis/util/DateUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import java.util.Calendar;
Expand Down Expand Up @@ -109,6 +111,9 @@ public class DateUtils {
ObjectArrays.concat(
SUPPORTED_DATE_ONLY_PARSERS, SUPPORTED_DATE_TIME_FORMAT_PARSERS, DateTimeParser.class);

private static final DateTimeFormatter ONLY_DATE_FORMATTER =
new DateTimeFormatterBuilder().append(null, SUPPORTED_DATE_ONLY_PARSERS).toFormatter();

private static final DateTimeFormatter DATE_FORMATTER =
new DateTimeFormatterBuilder().append(null, SUPPORTED_DATE_FORMAT_PARSERS).toFormatter();

Expand Down Expand Up @@ -662,8 +667,8 @@ public static String getPrettyInterval(Date start, Date end) {
}

/**
* Parses the given string into a Date using the supported date formats. Returns null if the
* string cannot be parsed.
* Parses the given string into a Date using the supported date formats. Add time at the beginning
* of the day if no time was provided. Returns null if the string cannot be parsed.
*
* @param dateString the date string.
* @return a date.
Expand All @@ -672,6 +677,29 @@ public static Date parseDate(String dateString) {
return safeParseDateTime(dateString, DATE_FORMATTER);
}

/**
* Parses the given string into a Date using the supported date formats. Add time at the end of
* the day if no time was provided. Returns null if the string cannot be parsed.
*
* @param dateString the date string.
* @return a date.
*/
public static Date parseDateEndOfTheDay(String dateString) {
if (StringUtils.isEmpty(dateString)) {
return null;
}

try {
Date date = safeParseDateTime(dateString, ONLY_DATE_FORMATTER);
LocalDateTime localDateTime =
LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()).with(LocalTime.MAX);
return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
} catch (IllegalArgumentException e) {
// dateString has time defined
teleivo marked this conversation as resolved.
Show resolved Hide resolved
}
return safeParseDateTime(dateString, DATE_FORMATTER);
}

/**
* Parses the given string into a Date using the supported date formats. Returns null if the
* string cannot be parsed.
Expand Down
16 changes: 16 additions & 0 deletions dhis-2/dhis-api/src/main/java/org/hisp/dhis/util/ObjectUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,22 @@ public static <T, U extends RuntimeException> T throwIfNull(T object, Supplier<U
return object;
}

/**
* If object is null return null, otherwise return function applied to object.
*
* @param object to check.
* @param <U> the function return type.
* @param <T> the object type.
* @param function the function to be applied to non-null object.
*/
public static <T, U> U applyIfNotNull(T object, Function<T, U> function) {
if (object == null) {
return null;
}

return function.apply(object);
}

/**
* Util method that always returns a new Set, either instantiated from a non-null Set passed as an
* argument, or if a null arg is passed then returning an empty Set. This helps reduce possible
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@
import org.hisp.dhis.notification.logging.NotificationLoggingService;
import org.hisp.dhis.notification.logging.NotificationValidationResult;
import org.hisp.dhis.program.Enrollment;
import org.hisp.dhis.program.EnrollmentService;
import org.hisp.dhis.program.Event;
import org.hisp.dhis.program.EventService;
import org.hisp.dhis.program.notification.ProgramNotificationTemplate;
import org.hisp.dhis.program.notification.ProgramNotificationTemplateService;
import org.hisp.dhis.rules.models.RuleAction;
Expand All @@ -62,10 +60,6 @@ abstract class NotificationRuleActionImplementer implements RuleActionImplemente

protected final NotificationLoggingService notificationLoggingService;

protected final EnrollmentService enrollmentService;

protected final EventService eventService;

protected ExternalNotificationLogEntry createLogEntry(String key, String templateUid) {
ExternalNotificationLogEntry entry = new ExternalNotificationLogEntry();
entry.setLastSentAt(new Date());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,7 @@
import org.hisp.dhis.notification.logging.NotificationTriggerEvent;
import org.hisp.dhis.notification.logging.NotificationValidationResult;
import org.hisp.dhis.program.Enrollment;
import org.hisp.dhis.program.EnrollmentService;
import org.hisp.dhis.program.Event;
import org.hisp.dhis.program.EventService;
import org.hisp.dhis.program.notification.ProgramNotificationInstance;
import org.hisp.dhis.program.notification.ProgramNotificationInstanceService;
import org.hisp.dhis.program.notification.ProgramNotificationTemplate;
Expand Down Expand Up @@ -70,15 +68,9 @@ public class RuleActionScheduleMessageImplementer extends NotificationRuleAction
public RuleActionScheduleMessageImplementer(
ProgramNotificationTemplateService programNotificationTemplateService,
NotificationLoggingService notificationLoggingService,
EnrollmentService enrollmentService,
EventService eventService,
ProgramNotificationInstanceService programNotificationInstanceService,
NotificationTemplateService notificationTemplateService) {
super(
programNotificationTemplateService,
notificationLoggingService,
enrollmentService,
eventService);
super(programNotificationTemplateService, notificationLoggingService);
this.programNotificationInstanceService = programNotificationInstanceService;
this.notificationTemplateService = notificationTemplateService;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,7 @@
import org.hisp.dhis.notification.logging.NotificationTriggerEvent;
import org.hisp.dhis.notification.logging.NotificationValidationResult;
import org.hisp.dhis.program.Enrollment;
import org.hisp.dhis.program.EnrollmentService;
import org.hisp.dhis.program.Event;
import org.hisp.dhis.program.EventService;
import org.hisp.dhis.program.notification.ProgramNotificationTemplate;
import org.hisp.dhis.program.notification.ProgramNotificationTemplateService;
import org.hisp.dhis.program.notification.event.ProgramRuleEnrollmentEvent;
Expand Down Expand Up @@ -72,14 +70,8 @@ public class RuleActionSendMessageImplementer extends NotificationRuleActionImpl
public RuleActionSendMessageImplementer(
ProgramNotificationTemplateService programNotificationTemplateService,
NotificationLoggingService notificationLoggingService,
EnrollmentService enrollmentService,
EventService eventService,
ApplicationEventPublisher publisher) {
super(
programNotificationTemplateService,
notificationLoggingService,
enrollmentService,
eventService);
super(programNotificationTemplateService, notificationLoggingService);
this.publisher = publisher;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,16 @@
import org.hisp.dhis.fieldfiltering.FieldPath;
import org.hisp.dhis.organisationunit.OrganisationUnit;
import org.hisp.dhis.program.Enrollment;
import org.hisp.dhis.program.Event;
import org.hisp.dhis.program.Program;
import org.hisp.dhis.program.ProgramStage;
import org.hisp.dhis.program.ProgramStatus;
import org.hisp.dhis.trackedentity.TrackedEntity;
import org.hisp.dhis.user.User;
import org.hisp.dhis.webapi.controller.event.webrequest.OrderCriteria;
import org.hisp.dhis.webapi.controller.tracker.export.PageRequestParams;
import org.hisp.dhis.webapi.webdomain.EndDateTime;
import org.hisp.dhis.webapi.webdomain.StartDateTime;

/**
* Represents query parameters sent to {@link EventsExportController}.
Expand Down Expand Up @@ -138,9 +141,9 @@ public class EventRequestParams implements PageRequestParams {

private Date scheduledBefore;

private Date updatedAfter;
private StartDateTime updatedAfter;

private Date updatedBefore;
private EndDateTime updatedBefore;

private String updatedWithin;

Expand Down Expand Up @@ -185,10 +188,10 @@ public class EventRequestParams implements PageRequestParams {
* @deprecated use {@link #events} instead which is comma instead of semicolon separated.
*/
@Deprecated(since = "2.41")
@OpenApi.Property({UID[].class, org.hisp.dhis.program.Event.class})
@OpenApi.Property({UID[].class, Event.class})
private String event;

@OpenApi.Property({UID[].class, org.hisp.dhis.program.Event.class})
@OpenApi.Property({UID[].class, Event.class})
private Set<UID> events = new HashSet<>();

/** Comma separated list of data element filters */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
package org.hisp.dhis.webapi.controller.tracker.export.event;

import static java.util.Collections.emptySet;
import static org.hisp.dhis.util.ObjectUtils.applyIfNotNull;
import static org.hisp.dhis.webapi.controller.tracker.export.RequestParamsValidator.parseFilters;
import static org.hisp.dhis.webapi.controller.tracker.export.RequestParamsValidator.validateDeprecatedParameter;
import static org.hisp.dhis.webapi.controller.tracker.export.RequestParamsValidator.validateDeprecatedUidsParameter;
Expand All @@ -48,6 +49,8 @@
import org.hisp.dhis.tracker.export.event.EventOperationParams.EventOperationParamsBuilder;
import org.hisp.dhis.util.DateUtils;
import org.hisp.dhis.webapi.controller.event.webrequest.OrderCriteria;
import org.hisp.dhis.webapi.webdomain.EndDateTime;
import org.hisp.dhis.webapi.webdomain.StartDateTime;
import org.springframework.stereotype.Component;

/**
Expand Down Expand Up @@ -114,22 +117,10 @@ public EventOperationParams map(EventRequestParams eventRequestParams)

EventOperationParamsBuilder builder =
EventOperationParams.builder()
.programUid(
eventRequestParams.getProgram() != null
? eventRequestParams.getProgram().getValue()
: null)
.programStageUid(
eventRequestParams.getProgramStage() != null
? eventRequestParams.getProgramStage().getValue()
: null)
.orgUnitUid(
eventRequestParams.getOrgUnit() != null
? eventRequestParams.getOrgUnit().getValue()
: null)
.trackedEntityUid(
eventRequestParams.getTrackedEntity() != null
? eventRequestParams.getTrackedEntity().getValue()
: null)
.programUid(applyIfNotNull(eventRequestParams.getProgram(), UID::getValue))
.programStageUid(applyIfNotNull(eventRequestParams.getProgramStage(), UID::getValue))
.orgUnitUid(applyIfNotNull(eventRequestParams.getOrgUnit(), UID::getValue))
.trackedEntityUid(applyIfNotNull(eventRequestParams.getTrackedEntity(), UID::getValue))
.programStatus(eventRequestParams.getProgramStatus())
.followUp(eventRequestParams.getFollowUp())
.orgUnitMode(orgUnitMode)
Expand All @@ -139,16 +130,17 @@ public EventOperationParams map(EventRequestParams eventRequestParams)
.occurredBefore(eventRequestParams.getOccurredBefore())
.scheduledAfter(eventRequestParams.getScheduledAfter())
.scheduledBefore(eventRequestParams.getScheduledBefore())
.updatedAfter(eventRequestParams.getUpdatedAfter())
.updatedBefore(eventRequestParams.getUpdatedBefore())
.updatedAfter(
applyIfNotNull(eventRequestParams.getUpdatedAfter(), StartDateTime::toDate))
.updatedBefore(
applyIfNotNull(eventRequestParams.getUpdatedBefore(), EndDateTime::toDate))
.updatedWithin(eventRequestParams.getUpdatedWithin())
.enrollmentEnrolledBefore(eventRequestParams.getEnrollmentEnrolledBefore())
.enrollmentEnrolledAfter(eventRequestParams.getEnrollmentEnrolledAfter())
.enrollmentOccurredBefore(eventRequestParams.getEnrollmentOccurredBefore())
.enrollmentOccurredAfter(eventRequestParams.getEnrollmentOccurredAfter())
.eventStatus(eventRequestParams.getStatus())
.attributeCategoryCombo(
attributeCategoryCombo != null ? attributeCategoryCombo.getValue() : null)
.attributeCategoryCombo(applyIfNotNull(attributeCategoryCombo, UID::getValue))
.attributeCategoryOptions(UID.toValueSet(attributeCategoryOptions))
.idSchemes(eventRequestParams.getIdSchemes())
.includeAttributes(false)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (c) 2004-2024, University of Oslo
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* Neither the name of the HISP project nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.hisp.dhis.webapi.webdomain;

import java.util.Date;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.hisp.dhis.util.DateUtils;

/**
* EndDateTime represents an upper limit date and time used to filter results in search APIs.
*
* <p>EndDateTime accepts any date and time in ISO8601 format. If no time is defined, then the time
* at the end of the day is used by default.
*
* <p>This behavior, combined with {@link StartDateTime}, allows to correctly implement an interval
* search including start and end dates.
*/
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class EndDateTime {
private final Date date;

public static EndDateTime valueOf(String date) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Javadoc on the list of valid patterns for date would be nice. Is this YYYYmmdd only?

return new EndDateTime(DateUtils.parseDateEndOfTheDay(date));
}

public Date toDate() {
if (date == null) {
return null;
}
return new Date(date.getTime());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (c) 2004-2024, University of Oslo
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* Neither the name of the HISP project nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.hisp.dhis.webapi.webdomain;

import java.util.Date;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.hisp.dhis.util.DateUtils;

/**
* StartDateTime represents a lower limit date and time used to filter results in search APIs.
*
* <p>StartDateTime accepts any date and time in ISO8601 format. If no time is defined, then the
* time at the beginning of the day is used by default.
*
* <p>This behavior, combined with {@link EndDateTime}, allows to correctly implement an interval
* search including start and end dates.
*/
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class StartDateTime {
private final Date date;

public static StartDateTime valueOf(String date) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a strong opinion but in newer Java APIs this is usually just called of, e.g. List.of, LocalDate.of

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

of should work as well with Springs conversion system as it looks for either a constructor or a factory called of, valueOf or from.

(see #16673)

return new StartDateTime(DateUtils.parseDate(date));
}

public Date toDate() {
if (date == null) {
return null;
}
return new Date(date.getTime());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,7 @@ class UIDBindingTest {

@BeforeEach
public void setUp() {
mockMvc =
MockMvcBuilders.standaloneSetup(new UIDController())
.setControllerAdvice(new CrudControllerAdvice())
.build();
mockMvc = MockMvcBuilders.standaloneSetup(new UIDController()).build();
}

@Test
Expand Down
Loading
Loading