From a99a884423eb72bea8670a4aa8032cf146aaa163 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Sun, 5 May 2024 08:40:30 +0200 Subject: [PATCH] add date parser for relative time notation --- .../pdb/plot/api/DateTimeRangeParser.java | 105 +++++++++++++++++ .../pdb/plot/api/DateTimeRangeParserTest.java | 107 ++++++++++++++++++ 2 files changed, 212 insertions(+) create mode 100644 pdb-plotting/src/main/java/org/lucares/pdb/plot/api/DateTimeRangeParser.java create mode 100644 pdb-plotting/src/test/java/org/lucares/pdb/plot/api/DateTimeRangeParserTest.java diff --git a/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/DateTimeRangeParser.java b/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/DateTimeRangeParser.java new file mode 100644 index 0000000..79ebd2c --- /dev/null +++ b/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/DateTimeRangeParser.java @@ -0,0 +1,105 @@ +package org.lucares.pdb.plot.api; + +import java.time.DayOfWeek; +import java.time.OffsetDateTime; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAdjusters; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.lucares.pdb.api.DateTimeRange; + +public class DateTimeRangeParser { + + public static DateTimeRange parse(final OffsetDateTime offsetTime, final String datePeriod) { + + final String[] startEnd = datePeriod.split(Pattern.quote("/")); + final String start = startEnd[0]; + final String end = startEnd[1]; + + final OffsetDateTime startTime = parseInternal(offsetTime, start); + final OffsetDateTime endTime = parseInternal(offsetTime, end); + + return new DateTimeRange(startTime, endTime); + } + + private static OffsetDateTime parseInternal(final OffsetDateTime offsetTime, final String timeDefinition) { + + final Pattern regex = Pattern.compile("(?[BE]?)(?\\-?[0-9]*)(?[mHDWMY])"); + + final Matcher matcher = regex.matcher(timeDefinition); + if (matcher.matches()) { + final String beginEnd = matcher.group("beginEnd"); + final boolean begin = "B".equals(beginEnd); + final String amountString = matcher.group("amount"); + final int amount = amountString.equals("") ? 0 : Integer.parseInt(amountString); + final String unitString = matcher.group("unit"); + + switch (unitString) { + case "m": { + final ChronoUnit unit = ChronoUnit.MINUTES; + if (begin) { + return offsetTime.plus(amount, unit).truncatedTo(unit); + } else { + return offsetTime.plus(amount + 1, unit).truncatedTo(unit).minusSeconds(1); + } + } + case "H": { + final ChronoUnit unit = ChronoUnit.HOURS; + if (begin) { + return offsetTime.plus(amount, unit).truncatedTo(unit); + } else { + return offsetTime.plus(amount + 1, unit).truncatedTo(unit).minusSeconds(1); + } + } + case "D": { + final ChronoUnit unit = ChronoUnit.DAYS; + if (begin) { + return offsetTime.plus(amount, unit).truncatedTo(unit); + } else { + return offsetTime.plus(amount + 1, unit).truncatedTo(unit).minusSeconds(1); + } + } + case "W": { + final DayOfWeek firstDayOfWeek = DayOfWeek.MONDAY; + final DayOfWeek lastDayOfWeek = DayOfWeek + .of(((firstDayOfWeek.getValue() - 1 + 6) % DayOfWeek.values().length) + 1); // weird + // computation, + // because DayOfWeek + // goes from 1 to 7 + final ChronoUnit unit = ChronoUnit.WEEKS; + if (begin) { + return offsetTime.plus(amount, unit).with(TemporalAdjusters.previousOrSame(firstDayOfWeek)) + .truncatedTo(ChronoUnit.DAYS); + } else { + return offsetTime.plus(amount, unit).with(TemporalAdjusters.nextOrSame(lastDayOfWeek)) + .plus(1, ChronoUnit.DAYS).truncatedTo(ChronoUnit.DAYS).minusSeconds(1); + } + } + case "M": { + final ChronoUnit unit = ChronoUnit.MONTHS; + if (begin) { + return offsetTime.plus(amount, unit).truncatedTo(ChronoUnit.DAYS).withDayOfMonth(1); + } else { + return offsetTime.plus(amount, unit).truncatedTo(ChronoUnit.DAYS).withDayOfMonth(1) + .plus(1, ChronoUnit.MONTHS).minusSeconds(1); + } + } + case "Y": { + final ChronoUnit unit = ChronoUnit.YEARS; + if (begin) { + return offsetTime.plus(amount, unit).truncatedTo(ChronoUnit.DAYS).withDayOfYear(1); + } else { + return offsetTime.plus(amount, unit).truncatedTo(ChronoUnit.DAYS).withDayOfYear(1) + .plus(1, ChronoUnit.YEARS).minusSeconds(1); + } + } + default: + throw new IllegalArgumentException("Unexpected value: " + unitString); + } + } + + throw new IllegalArgumentException("invalid input: " + timeDefinition); + } + +} diff --git a/pdb-plotting/src/test/java/org/lucares/pdb/plot/api/DateTimeRangeParserTest.java b/pdb-plotting/src/test/java/org/lucares/pdb/plot/api/DateTimeRangeParserTest.java new file mode 100644 index 0000000..d934c74 --- /dev/null +++ b/pdb-plotting/src/test/java/org/lucares/pdb/plot/api/DateTimeRangeParserTest.java @@ -0,0 +1,107 @@ +package org.lucares.pdb.plot.api; + +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Assertions; +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; +import org.lucares.pdb.api.DateTimeRange; + +public class DateTimeRangeParserTest { + + @Test + public void test() { + + } + + public static Stream providerDatePeriods() { + + return Stream.of(// + + // last 15 minutes + Arguments.of("2000-01-02 12:59:59", "B-14m/Em", "2000-01-02 12:45:00", "2000-01-02 12:59:59"), + + // this hour + Arguments.of("2000-01-02 12:00:00", "BH/EH", "2000-01-02 12:00:00", "2000-01-02 12:59:59"), + Arguments.of("2000-01-02 12:59:59", "BH/EH", "2000-01-02 12:00:00", "2000-01-02 12:59:59"), + + // previous hour + Arguments.of("2000-01-02 12:00:00", "B-1H/E-1H", "2000-01-02 11:00:00", "2000-01-02 11:59:59"), + Arguments.of("2000-01-02 12:59:58", "B-1H/E-1H", "2000-01-02 11:00:00", "2000-01-02 11:59:59"), + Arguments.of("2000-01-02 12:59:59", "B-1H/E-1H", "2000-01-02 11:00:00", "2000-01-02 11:59:59"), + + // today + Arguments.of("2000-01-02 12:00:00", "BD/ED", "2000-01-02 00:00:00", "2000-01-02 23:59:59"), + Arguments.of("2000-01-02 00:00:00", "BD/ED", "2000-01-02 00:00:00", "2000-01-02 23:59:59"), + Arguments.of("2000-01-02 23:59:59", "BD/ED", "2000-01-02 00:00:00", "2000-01-02 23:59:59"), + Arguments.of("2000-01-02 12:00:00", "B0D/E0D", "2000-01-02 00:00:00", "2000-01-02 23:59:59"), + + // tomorrow + Arguments.of("2000-01-02 12:00:00", "B1D/E1D", "2000-01-03 00:00:00", "2000-01-03 23:59:59"), + + // yesterday + Arguments.of("2000-01-02 12:00:00", "B-1D/E-1D", "2000-01-01 00:00:00", "2000-01-01 23:59:59"), + + // this week + Arguments.of("2024-04-22 12:00:00", "BW/EW", "2024-04-22 00:00:00", "2024-04-28 23:59:59"), + Arguments.of("2024-04-23 12:00:00", "BW/EW", "2024-04-22 00:00:00", "2024-04-28 23:59:59"), + Arguments.of("2024-04-24 12:00:00", "BW/EW", "2024-04-22 00:00:00", "2024-04-28 23:59:59"), + Arguments.of("2024-04-25 12:00:00", "BW/EW", "2024-04-22 00:00:00", "2024-04-28 23:59:59"), + Arguments.of("2024-04-26 12:00:00", "BW/EW", "2024-04-22 00:00:00", "2024-04-28 23:59:59"), + Arguments.of("2024-04-27 12:00:00", "BW/EW", "2024-04-22 00:00:00", "2024-04-28 23:59:59"), + Arguments.of("2024-04-28 12:00:00", "BW/EW", "2024-04-22 00:00:00", "2024-04-28 23:59:59"), + + // previous week + Arguments.of("2024-04-29 12:00:00", "B-1W/E-1W", "2024-04-22 00:00:00", "2024-04-28 23:59:59"), + + // last 4 week (including this one + Arguments.of("2024-04-28 12:00:00", "B-3W/EW", "2024-04-01 00:00:00", "2024-04-28 23:59:59"), + + // this month + Arguments.of("2024-04-29 12:00:00", "BM/EM", "2024-04-01 00:00:00", "2024-04-30 23:59:59"), + + // previous month (in a leap year) + Arguments.of("2023-03-29 12:00:00", "B-1M/E-1M", "2023-02-01 00:00:00", "2023-02-28 23:59:59"), + + // previous month (in a leap year) + Arguments.of("2024-03-29 12:00:00", "B-1M/E-1M", "2024-02-01 00:00:00", "2024-02-29 23:59:59"), + + // last 3 months + Arguments.of("2024-03-29 12:00:00", "B-2M/EM", "2024-01-01 00:00:00", "2024-03-31 23:59:59"), + + // previous 3 months (in a leap year) + Arguments.of("2024-03-29 12:00:00", "B-3M/E-1M", "2023-12-01 00:00:00", "2024-02-29 23:59:59"), + + // this year + Arguments.of("2024-03-29 12:00:00", "BY/EY", "2024-01-01 00:00:00", "2024-12-31 23:59:59"), + + // previous year + Arguments.of("2024-03-29 12:00:00", "B-1Y/E-1Y", "2023-01-01 00:00:00", "2023-12-31 23:59:59") + + // + // Arguments.of("2024-03-29 12:00:00", "B-1H-15m/Bm", "2024-03-29 10:45:00", + // "2024-03-29 12:00:00") + + ); + } + + @ParameterizedTest + @MethodSource("providerDatePeriods") + public void testDatePeriods(final String now, final String datePeriod, final String expectedStart, + final String expectedEnd) throws Exception { + final OffsetDateTime offsetTime = LocalDateTime.parse(now, PlotSettings.DATE_FORMAT).atOffset(ZoneOffset.UTC); + + final DateTimeRange actual = DateTimeRangeParser.parse(offsetTime, datePeriod); + + final String actualStart = PlotSettings.DATE_FORMAT.format(actual.getStart()); + final String actualEnd = PlotSettings.DATE_FORMAT.format(actual.getEnd()); + System.out.println("at " + now + " " + datePeriod + " -> " + actualStart + " - " + actualEnd); + Assertions.assertEquals(expectedStart, actualStart, "start"); + Assertions.assertEquals(expectedEnd, actualEnd, "end"); + } +}