Skip to content

Commit 93cb82f

Browse files
[CYB-205] leap year DateUtils fix (#71)
* [CYB-205] leap year DateUtils fix + tests
1 parent ef1d7e5 commit 93cb82f

File tree

4 files changed

+103
-24
lines changed

4 files changed

+103
-24
lines changed

flink-cyber/flink-parse/flink-parse-metron/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,12 @@
108108
<version>${jupiter.junit.version}</version>
109109
<scope>test</scope>
110110
</dependency>
111+
<dependency>
112+
<groupId>org.mockito</groupId>
113+
<artifactId>mockito-inline</artifactId>
114+
<version>${mockito.version}</version>
115+
<scope>test</scope>
116+
</dependency>
111117
<dependency>
112118
<groupId>org.junit.jupiter</groupId>
113119
<artifactId>junit-jupiter-migrationsupport</artifactId>

flink-cyber/flink-parse/flink-parse-metron/src/main/java/org/apache/metron/parsers/utils/DateUtils.java

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@
2020

2121
import java.text.ParseException;
2222
import java.text.SimpleDateFormat;
23-
import java.time.ZonedDateTime;
23+
import java.time.*;
24+
import java.time.format.DateTimeFormatter;
25+
import java.time.format.DateTimeFormatterBuilder;
26+
import java.time.format.DateTimeParseException;
27+
import java.time.temporal.ChronoField;
28+
import java.time.temporal.TemporalAccessor;
2429
import java.util.ArrayList;
2530
import java.util.Calendar;
2631
import java.util.Date;
@@ -39,25 +44,29 @@ public class DateUtils {
3944
// Per IBM LEEF guide at https://www.ibm.com/support/knowledgecenter/SS42VS_DSM/c_LEEF_Format_Guide_intro.html
4045
public static List<SimpleDateFormat> DATE_FORMATS_LEEF = new ArrayList<SimpleDateFormat>() {
4146
{
42-
add(new SimpleDateFormat("MMM dd yyyy HH:mm:ss.SSS zzz"));
43-
add(new SimpleDateFormat("MMM dd yyyy HH:mm:ss.SSS"));
44-
add(new SimpleDateFormat("MMM dd yyyy HH:mm:ss"));
47+
add(new SimpleDateFormat("MMM d yyyy HH:mm:ss.SSS Z"));
48+
add(new SimpleDateFormat("MMM d yyyy HH:mm:ss.SSS z"));
49+
add(new SimpleDateFormat("MMM d yyyy HH:mm:ss.SSS"));
50+
add(new SimpleDateFormat("MMM d yyyy HH:mm:ss"));
4551
}
4652
};
4753

4854
public static List<SimpleDateFormat> DATE_FORMATS_CEF = new ArrayList<SimpleDateFormat>() {
4955
{
5056
// as per CEF Spec
51-
add(new SimpleDateFormat("MMM dd HH:mm:ss.SSS zzz"));
52-
add(new SimpleDateFormat("MMM dd HH:mm:ss.SSS"));
53-
add(new SimpleDateFormat("MMM dd HH:mm:ss zzz"));
54-
add(new SimpleDateFormat("MMM dd HH:mm:ss"));
55-
add(new SimpleDateFormat("MMM dd yyyy HH:mm:ss.SSS zzz"));
56-
add(new SimpleDateFormat("MMM dd yyyy HH:mm:ss.SSS"));
57-
add(new SimpleDateFormat("MMM dd yyyy HH:mm:ss zzz"));
58-
add(new SimpleDateFormat("MMM dd yyyy HH:mm:ss"));
57+
add(new SimpleDateFormat("MMM d HH:mm:ss.SSS Z"));
58+
add(new SimpleDateFormat("MMM d HH:mm:ss.SSS z"));
59+
add(new SimpleDateFormat("MMM d HH:mm:ss.SSS"));
60+
add(new SimpleDateFormat("MMM d HH:mm:ss zzz"));
61+
add(new SimpleDateFormat("MMM d HH:mm:ss"));
62+
add(new SimpleDateFormat("MMM d yyyy HH:mm:ss.SSS Z"));
63+
add(new SimpleDateFormat("MMM d yyyy HH:mm:ss.SSS z"));
64+
add(new SimpleDateFormat("MMM d yyyy HH:mm:ss.SSS"));
65+
add(new SimpleDateFormat("MMM d yyyy HH:mm:ss Z"));
66+
add(new SimpleDateFormat("MMM d yyyy HH:mm:ss z"));
67+
add(new SimpleDateFormat("MMM d yyyy HH:mm:ss"));
5968
// found in the wild
60-
add(new SimpleDateFormat("dd MMMM yyyy HH:mm:ss"));
69+
add(new SimpleDateFormat("d MMMM yyyy HH:mm:ss"));
6170
}
6271
};
6372

@@ -103,22 +112,33 @@ public static long parseMultiformat(String candidate, List<SimpleDateFormat> val
103112
} else {
104113
for (SimpleDateFormat pattern : validPatterns) {
105114
try {
106-
Calendar cal = Calendar.getInstance();
107-
cal.setTime(pattern.parse(candidate));
108-
Calendar current = Calendar.getInstance();
109-
if (cal.get(Calendar.YEAR) == 1970) {
110-
cal.set(Calendar.YEAR, current.get(Calendar.YEAR));
115+
DateTimeFormatterBuilder formatterBuilder = new DateTimeFormatterBuilder()
116+
.appendPattern(pattern.toPattern());
117+
if (!pattern.toPattern().contains("y")) {
118+
formatterBuilder
119+
.parseDefaulting(ChronoField.YEAR, LocalDate.now().getYear());
111120
}
112-
current.add(Calendar.DAY_OF_MONTH, 4);
113-
if (cal.after(current)) {
114-
cal.add(Calendar.YEAR, -1);
121+
DateTimeFormatter formatter = formatterBuilder.toFormatter();
122+
ZonedDateTime parsedValue = parseDateTimeWithDefaultTimezone(candidate, formatter);
123+
ZonedDateTime current = ZonedDateTime.now(parsedValue.getZone());
124+
125+
current = current.plusDays(4);
126+
if (parsedValue.isAfter(current)) {
127+
parsedValue = parsedValue.minusYears(1);
115128
}
116-
return cal.getTimeInMillis();
117-
} catch (ParseException e) {
129+
return parsedValue.toInstant().toEpochMilli();
130+
} catch (DateTimeParseException e) {
118131
continue;
119132
}
120133
}
121134
throw new ParseException("Failed to parse any of the given date formats", 0);
122135
}
123136
}
137+
138+
private static ZonedDateTime parseDateTimeWithDefaultTimezone(String candidate, DateTimeFormatter formatter) {
139+
TemporalAccessor temporalAccessor = formatter.parseBest(candidate, ZonedDateTime::from, LocalDateTime::from);
140+
return temporalAccessor instanceof ZonedDateTime
141+
? ((ZonedDateTime) temporalAccessor)
142+
: ((LocalDateTime) temporalAccessor).atZone(ZoneId.systemDefault());
143+
}
124144
}

flink-cyber/flink-parse/flink-parse-metron/src/test/java/org/apache/metron/parsers/cef/CEFParserTest.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,22 @@
3232
import org.json.simple.parser.JSONParser;
3333
import org.junit.jupiter.api.BeforeEach;
3434
import org.junit.jupiter.api.Test;
35+
import org.mockito.MockedStatic;
36+
import org.mockito.Mockito;
3537

3638
import java.io.IOException;
3739
import java.net.URL;
3840
import java.nio.charset.StandardCharsets;
3941
import java.text.SimpleDateFormat;
42+
import java.time.LocalDate;
43+
import java.time.ZoneId;
44+
import java.time.temporal.TemporalAccessor;
4045
import java.util.*;
4146

42-
import static org.hamcrest.CoreMatchers.equalTo;
47+
import static org.hamcrest.CoreMatchers.*;
4348
import static org.hamcrest.MatcherAssert.assertThat;
4449
import static org.junit.jupiter.api.Assertions.*;
50+
import static org.mockito.Mockito.when;
4551

4652
public class CEFParserTest {
4753
private CEFParser parser;
@@ -159,6 +165,27 @@ public void testMissingYearFromDate() throws java.text.ParseException {
159165
runMissingYear(correct, current);
160166
}
161167

168+
@Test
169+
public void testMissingLeapYearFromDate() {
170+
Calendar current = Calendar.getInstance();
171+
Calendar correct = Calendar.getInstance();
172+
173+
current.set(Calendar.YEAR, 2024);
174+
current.set(Calendar.MONTH, 1);
175+
current.set(Calendar.DAY_OF_MONTH, 29);
176+
177+
correct.setTimeInMillis(current.getTimeInMillis());
178+
179+
LocalDate fakeNowDate = LocalDate.of(2024, 2, 29);
180+
try (MockedStatic<LocalDate> dateContext = Mockito.mockStatic(LocalDate.class, Mockito.CALLS_REAL_METHODS)) {
181+
dateContext.when(LocalDate::now).thenReturn(fakeNowDate);
182+
assertEquals(fakeNowDate, LocalDate.now());
183+
assertNotNull(fakeNowDate);
184+
185+
runMissingYear(correct, current);
186+
}
187+
}
188+
162189
@Test
163190
public void testFourDayFutureBecomesPast() {
164191
Calendar current = Calendar.getInstance();

flink-cyber/flink-parse/flink-parse-metron/src/test/java/org/apache/metron/parsers/leef/LEEFParserTest.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,20 @@
2727
import org.json.simple.parser.JSONParser;
2828
import org.junit.jupiter.api.BeforeEach;
2929
import org.junit.jupiter.api.Test;
30+
import org.mockito.MockedStatic;
31+
import org.mockito.Mockito;
3032

3133
import java.nio.charset.StandardCharsets;
3234
import java.text.DateFormat;
3335
import java.text.SimpleDateFormat;
36+
import java.time.LocalDate;
37+
import java.time.ZoneId;
3438
import java.util.*;
3539

3640
import static org.hamcrest.CoreMatchers.equalTo;
3741
import static org.hamcrest.MatcherAssert.assertThat;
3842
import static org.junit.jupiter.api.Assertions.*;
43+
import static org.mockito.Mockito.when;
3944

4045
public class LEEFParserTest {
4146
private LEEFParser parser;
@@ -109,6 +114,27 @@ public void testMissingYearFromDate() {
109114
runMissingYear(correct, current);
110115
}
111116

117+
@Test
118+
public void testMissingLeapYearFromDate() {
119+
Calendar current = Calendar.getInstance();
120+
Calendar correct = Calendar.getInstance();
121+
122+
current.set(Calendar.YEAR, 2024);
123+
current.set(Calendar.MONTH, 1);
124+
current.set(Calendar.DAY_OF_MONTH, 29);
125+
126+
correct.setTimeInMillis(current.getTimeInMillis());
127+
128+
LocalDate fakeNowDate = LocalDate.of(2024, 2, 29);
129+
try (MockedStatic<LocalDate> dateContext = Mockito.mockStatic(LocalDate.class, Mockito.CALLS_REAL_METHODS)) {
130+
dateContext.when(LocalDate::now).thenReturn(fakeNowDate);
131+
assertEquals(fakeNowDate, LocalDate.now());
132+
assertNotNull(fakeNowDate);
133+
134+
runMissingYear(correct, current);
135+
}
136+
}
137+
112138
@Test
113139
public void testFourDayFutureBecomesPast() {
114140
Calendar current = Calendar.getInstance();

0 commit comments

Comments
 (0)