Skip to content

Commit f081dfe

Browse files
authored
Merge pull request #50 from sidhant92/date_time
Date/DateTime Support
2 parents c203d36 + 5ad114f commit f081dfe

23 files changed

+962
-338
lines changed

README.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ The language supports the following data types:
6868
4. **Decimal**: Numbers with decimal points (stored as BigDecimal). Examples: `3.14`, `-2.5`
6969
5. **Boolean**: `true` or `false` (case-insensitive)
7070
6. **Version**: Semantic version numbers. Examples: `1.0.6`, `2.3.1.5`
71-
7. **Null**: Represented as `null`
71+
7. **Date**: Date values in YYYY-MM-DD format. Examples: `2023-12-25`, `2024-01-15`
72+
8. **DateTime**: Date and time values in YYYY-MM-DD HH:MM:SS format. Examples: `2023-12-25 14:30:00`, `2024-01-15 09:15:30`
73+
9. **Null**: Represented as `null`
7274

7375
### Field References
7476

@@ -174,6 +176,7 @@ permissions CONTAINS_ALL ('read', 'write')
174176
| `MODE` | Mode of values | `MODE(1, 1, 2, 3)` or `MODE(numbers)` |
175177
| `LEN` | Length of string or array | `LEN('hello')` or `LEN(items)` |
176178
| `INT` | Convert to integer | `INT(2.7)` or `INT(value)` |
179+
| `DAYS_ELAPSED` | Number of days elapsed since a date | `DAYS_ELAPSED(2023-01-01)` or `DAYS_ELAPSED(startDate)` |
177180

178181
Functions can be used in both boolean and arithmetic contexts:
179182

@@ -233,6 +236,24 @@ permissions CONTAINS_ALL ('read', 'write', 'delete')
233236
LEN(items) > 0
234237
```
235238

239+
#### Working with Dates
240+
241+
Date and DateTime values can be used in comparisons and with the DAYS_ELAPSED function:
242+
243+
```
244+
// Date comparisons
245+
startDate > 2023-01-01
246+
endDate <= 2024-12-31
247+
248+
// DateTime comparisons
249+
lastLogin >= 2023-06-15 09:30:00
250+
createdAt < 2024-01-01 00:00:00
251+
252+
// Using DAYS_ELAPSED function
253+
DAYS_ELAPSED(startDate) > 30
254+
DAYS_ELAPSED(2023-01-01) < 365
255+
```
256+
236257
#### String Handling
237258

238259
- Strings must be enclosed in single or double quotes

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ repositories {
3232

3333
dependencies {
3434
implementation 'org.apache.maven:maven-artifact:3.5.2'
35-
implementation 'org.antlr:antlr4-runtime:4.13.1'
35+
implementation 'org.antlr:antlr4-runtime:4.13.2'
3636
implementation 'io.vavr:vavr:0.10.4'
3737
implementation 'com.github.ben-manes.caffeine:caffeine:2.9.3'
3838
implementation 'org.projectlombok:lombok:1.18.26'

src/main/java/com/github/sidhant92/boolparser/constant/DataType.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@
88
*/
99
@AllArgsConstructor
1010
public enum DataType {
11-
STRING(6, false),
11+
STRING(8, false),
1212
INTEGER(3, true),
1313
LONG(4, true),
1414
DECIMAL(5, true),
1515
VERSION(2, true),
16-
BOOLEAN(1, false);
16+
BOOLEAN(1, false),
17+
DATE(6, false),
18+
DATETIME(7, false);
1719

1820
public final int priority;
1921

src/main/java/com/github/sidhant92/boolparser/constant/FunctionType.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ public enum FunctionType {
1616
MODE,
1717
MEDIAN,
1818
INT,
19-
LEN;
19+
LEN,
20+
DAYS_ELAPSED;
2021

2122
public static Optional<FunctionType> getArrayFunctionFromSymbol(final String symbol) {
2223
final String symbolUpperCase = symbol.toUpperCase();

src/main/java/com/github/sidhant92/boolparser/datatype/DataTypeFactory.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ public static void initialize() {
2222
abstractDataTypeMap.put(DataType.LONG, new LongDataType());
2323
abstractDataTypeMap.put(DataType.VERSION, new VersionDataType());
2424
abstractDataTypeMap.put(DataType.BOOLEAN, new BooleanDataType());
25+
abstractDataTypeMap.put(DataType.DATE, new DateDataType());
26+
abstractDataTypeMap.put(DataType.DATETIME, new DateTimeDataType());
2527
}
2628

2729
public static AbstractDataType getDataType(final DataType dataType) {
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.github.sidhant92.boolparser.datatype;
2+
3+
import java.time.LocalDate;
4+
import java.time.format.DateTimeFormatter;
5+
import java.time.format.DateTimeParseException;
6+
import java.util.Optional;
7+
import com.github.sidhant92.boolparser.constant.DataType;
8+
9+
/**
10+
* @author sidhant.aggarwal
11+
* @since 05/03/2023
12+
*/
13+
public class DateDataType extends AbstractDataType<LocalDate> {
14+
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
15+
16+
public DateDataType() {
17+
super(LocalDate.class);
18+
}
19+
20+
@Override
21+
public DataType getDataType() {
22+
return DataType.DATE;
23+
}
24+
25+
@Override
26+
public boolean isValid(final Object value) {
27+
boolean isValid = super.defaultIsValid(value);
28+
if (!isValid) {
29+
return parseDate(value.toString()).isPresent();
30+
}
31+
return true;
32+
}
33+
34+
@Override
35+
public boolean isValid(final Object value, final boolean useStrictValidation) {
36+
if (!useStrictValidation) {
37+
return isValid(value);
38+
}
39+
return super.defaultIsValid(value);
40+
}
41+
42+
@Override
43+
public Optional<LocalDate> getValue(Object value) {
44+
final Optional<LocalDate> result = defaultGetValue(value);
45+
if (result.isPresent()) {
46+
return result;
47+
}
48+
return parseDate(value.toString());
49+
}
50+
51+
private Optional<LocalDate> parseDate(String dateString) {
52+
try {
53+
return Optional.of(LocalDate.parse(dateString, DATE_FORMATTER));
54+
} catch (DateTimeParseException ignored) {
55+
return Optional.empty();
56+
}
57+
}
58+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.github.sidhant92.boolparser.datatype;
2+
3+
import java.time.LocalDateTime;
4+
import java.time.format.DateTimeFormatter;
5+
import java.time.format.DateTimeParseException;
6+
import java.util.Optional;
7+
import com.github.sidhant92.boolparser.constant.DataType;
8+
9+
/**
10+
* @author sidhant.aggarwal
11+
* @since 05/03/2023
12+
*/
13+
public class DateTimeDataType extends AbstractDataType<LocalDateTime> {
14+
private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
15+
16+
public DateTimeDataType() {
17+
super(LocalDateTime.class);
18+
}
19+
20+
@Override
21+
public DataType getDataType() {
22+
return DataType.DATETIME;
23+
}
24+
25+
@Override
26+
public boolean isValid(final Object value) {
27+
boolean isValid = super.defaultIsValid(value);
28+
if (!isValid) {
29+
return parseDateTime(value.toString()).isPresent();
30+
}
31+
return true;
32+
}
33+
34+
@Override
35+
public boolean isValid(final Object value, final boolean useStrictValidation) {
36+
if (!useStrictValidation) {
37+
return isValid(value);
38+
}
39+
return super.defaultIsValid(value);
40+
}
41+
42+
@Override
43+
public Optional<LocalDateTime> getValue(Object value) {
44+
final Optional<LocalDateTime> result = defaultGetValue(value);
45+
if (result.isPresent()) {
46+
return result;
47+
}
48+
return parseDateTime(value.toString());
49+
}
50+
51+
private Optional<LocalDateTime> parseDateTime(String dateTimeString) {
52+
try {
53+
return Optional.of(LocalDateTime.parse(dateTimeString, DATETIME_FORMATTER));
54+
} catch (DateTimeParseException ignored) {
55+
return Optional.empty();
56+
}
57+
}
58+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.github.sidhant92.boolparser.exception;
2+
3+
public class InvalidFunctionArgument extends RuntimeException {
4+
public InvalidFunctionArgument(final String message) {
5+
super(message);
6+
}
7+
8+
public InvalidFunctionArgument() {
9+
super();
10+
}
11+
}

src/main/java/com/github/sidhant92/boolparser/function/FunctionFactory.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.github.sidhant92.boolparser.constant.FunctionType;
66
import com.github.sidhant92.boolparser.function.arithmetic.AbstractFunction;
77
import com.github.sidhant92.boolparser.function.arithmetic.AvgFunction;
8+
import com.github.sidhant92.boolparser.function.arithmetic.DaysElapsedFunction;
89
import com.github.sidhant92.boolparser.function.arithmetic.IntFunction;
910
import com.github.sidhant92.boolparser.function.arithmetic.LenFunction;
1011
import com.github.sidhant92.boolparser.function.arithmetic.MaxFunction;
@@ -35,6 +36,7 @@ public static void initialize() {
3536
arithmeticFunctionrMap.put(FunctionType.MODE, new ModeFunction());
3637
arithmeticFunctionrMap.put(FunctionType.INT, new IntFunction());
3738
arithmeticFunctionrMap.put(FunctionType.LEN, new LenFunction());
39+
arithmeticFunctionrMap.put(FunctionType.DAYS_ELAPSED, new DaysElapsedFunction());
3840
}
3941

4042
public static AbstractFunction getArithmeticFunction(final FunctionType functionType) {
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.github.sidhant92.boolparser.function.arithmetic;
2+
3+
import java.time.LocalDate;
4+
import java.time.temporal.ChronoUnit;
5+
import java.util.Arrays;
6+
import java.util.List;
7+
import com.github.sidhant92.boolparser.constant.ContainerDataType;
8+
import com.github.sidhant92.boolparser.constant.DataType;
9+
import com.github.sidhant92.boolparser.constant.FunctionType;
10+
import com.github.sidhant92.boolparser.domain.EvaluatedNode;
11+
import com.github.sidhant92.boolparser.exception.InvalidFunctionArgument;
12+
13+
/**
14+
* Function to calculate the number of days elapsed since a given date.
15+
* Returns a positive number for dates in the past, negative for dates in the future.
16+
*
17+
* @author sidhant.aggarwal
18+
* @since 05/03/2023
19+
*/
20+
public class DaysElapsedFunction extends AbstractFunction {
21+
22+
@Override
23+
public Object evaluate(final List<EvaluatedNode> items) {
24+
if (items.size() != 1) {
25+
throw new InvalidFunctionArgument("DAYS_ELAPSED function requires exactly one argument");
26+
}
27+
28+
final EvaluatedNode item = items.get(0);
29+
final Object value = item.getValue();
30+
31+
LocalDate inputDate;
32+
if (value instanceof LocalDate) {
33+
inputDate = (LocalDate) value;
34+
} else if (value instanceof String) {
35+
// Try to parse string as date if the DataType detection missed it
36+
try {
37+
inputDate = LocalDate.parse((String) value);
38+
} catch (Exception e) {
39+
throw new InvalidFunctionArgument("DAYS_ELAPSED function requires a valid date argument, got: " + value);
40+
}
41+
} else {
42+
throw new InvalidFunctionArgument("DAYS_ELAPSED function requires a date argument, got: " + value.getClass().getSimpleName());
43+
}
44+
45+
final LocalDate today = LocalDate.now();
46+
return ChronoUnit.DAYS.between(inputDate, today);
47+
}
48+
49+
@Override
50+
public FunctionType getFunctionType() {
51+
return FunctionType.DAYS_ELAPSED;
52+
}
53+
54+
@Override
55+
public List<ContainerDataType> getAllowedContainerTypes() {
56+
return Arrays.asList(ContainerDataType.PRIMITIVE);
57+
}
58+
59+
@Override
60+
public List<DataType> getAllowedDataTypes() {
61+
return Arrays.asList(DataType.DATE);
62+
}
63+
}

0 commit comments

Comments
 (0)