Skip to content

Commit 18c7060

Browse files
committed
Use thread safe DateTimeFormatter instead of SimpleDateFormat
Signed-off-by: David Frizelle <[email protected]>
1 parent 8caffe8 commit 18c7060

File tree

5 files changed

+46
-37
lines changed

5 files changed

+46
-37
lines changed

spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/filter/converter/SimpleVectorStoreFilterExpressionConverter.java

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@
1616

1717
package org.springframework.ai.vectorstore.filter.converter;
1818

19-
import java.text.ParseException;
20-
import java.text.SimpleDateFormat;
19+
import java.time.ZoneOffset;
20+
import java.time.format.DateTimeFormatter;
21+
import java.time.format.DateTimeParseException;
2122
import java.util.Date;
2223
import java.util.List;
23-
import java.util.TimeZone;
2424
import java.util.regex.Pattern;
2525

2626
import org.springframework.ai.vectorstore.filter.Filter;
@@ -36,11 +36,10 @@ public class SimpleVectorStoreFilterExpressionConverter extends AbstractFilterEx
3636

3737
private static final Pattern DATE_FORMAT_PATTERN = Pattern.compile("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z");
3838

39-
private final SimpleDateFormat dateFormat;
39+
private final DateTimeFormatter dateFormat;
4040

4141
public SimpleVectorStoreFilterExpressionConverter() {
42-
this.dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
43-
this.dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
42+
this.dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'").withZone(ZoneOffset.UTC);
4443
}
4544

4645
@Override
@@ -113,17 +112,17 @@ private void appendSpELContains(StringBuilder formattedList, StringBuilder conte
113112
protected void doSingleValue(Object value, StringBuilder context) {
114113
if (value instanceof Date date) {
115114
context.append("'");
116-
context.append(this.dateFormat.format(date));
115+
context.append(this.dateFormat.format(date.toInstant()));
117116
context.append("'");
118117
}
119118
else if (value instanceof String text) {
120119
context.append("'");
121120
if (DATE_FORMAT_PATTERN.matcher(text).matches()) {
122121
try {
123-
Date date = this.dateFormat.parse(text);
122+
var date = this.dateFormat.parse(text);
124123
context.append(this.dateFormat.format(date));
125124
}
126-
catch (ParseException e) {
125+
catch (DateTimeParseException e) {
127126
throw new IllegalArgumentException("Invalid date type:" + text, e);
128127
}
129128
}

vector-stores/spring-ai-azure-store/src/main/java/org/springframework/ai/vectorstore/azure/AzureAiSearchFilterExpressionConverter.java

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@
1616

1717
package org.springframework.ai.vectorstore.azure;
1818

19-
import java.text.ParseException;
20-
import java.text.SimpleDateFormat;
19+
import java.time.ZoneOffset;
20+
import java.time.format.DateTimeFormatter;
21+
import java.time.format.DateTimeParseException;
2122
import java.util.Date;
2223
import java.util.List;
23-
import java.util.TimeZone;
2424
import java.util.regex.Pattern;
2525

2626
import org.springframework.ai.vectorstore.azure.AzureVectorStore.MetadataField;
@@ -40,18 +40,17 @@
4040
*/
4141
public class AzureAiSearchFilterExpressionConverter extends AbstractFilterExpressionConverter {
4242

43-
private static Pattern DATE_FORMAT_PATTERN = Pattern.compile("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z");
43+
private static final Pattern DATE_FORMAT_PATTERN = Pattern.compile("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z");
4444

45-
private final SimpleDateFormat dateFormat;
45+
private final DateTimeFormatter dateFormat;
4646

4747
private List<String> allowedIdentifierNames;
4848

4949
public AzureAiSearchFilterExpressionConverter(List<MetadataField> filterMetadataFields) {
5050
Assert.notNull(filterMetadataFields, "The filterMetadataFields can not null.");
5151

5252
this.allowedIdentifierNames = filterMetadataFields.stream().map(MetadataField::name).toList();
53-
this.dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
54-
this.dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
53+
this.dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'").withZone(ZoneOffset.UTC);
5554
}
5655

5756
@Override
@@ -137,15 +136,15 @@ protected void doValue(Filter.Value filterValue, StringBuilder context) {
137136
@Override
138137
protected void doSingleValue(Object value, StringBuilder context) {
139138
if (value instanceof Date date) {
140-
context.append(this.dateFormat.format(date));
139+
context.append(this.dateFormat.format(date.toInstant()));
141140
}
142141
else if (value instanceof String text) {
143142
if (DATE_FORMAT_PATTERN.matcher(text).matches()) {
144143
try {
145-
Date date = this.dateFormat.parse(text);
144+
var date = this.dateFormat.parse(text);
146145
context.append(this.dateFormat.format(date));
147146
}
148-
catch (ParseException e) {
147+
catch (DateTimeParseException e) {
149148
throw new IllegalArgumentException("Invalid date type:" + text, e);
150149
}
151150
}

vector-stores/spring-ai-elasticsearch-store/src/main/java/org/springframework/ai/vectorstore/elasticsearch/ElasticsearchAiSearchFilterExpressionConverter.java

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@
1616

1717
package org.springframework.ai.vectorstore.elasticsearch;
1818

19-
import java.text.ParseException;
20-
import java.text.SimpleDateFormat;
19+
import java.time.ZoneOffset;
20+
import java.time.format.DateTimeFormatter;
21+
import java.time.format.DateTimeParseException;
2122
import java.util.Date;
2223
import java.util.List;
23-
import java.util.TimeZone;
2424
import java.util.regex.Pattern;
2525

2626
import org.springframework.ai.vectorstore.filter.Filter;
@@ -40,11 +40,10 @@ public class ElasticsearchAiSearchFilterExpressionConverter extends AbstractFilt
4040

4141
private static final Pattern DATE_FORMAT_PATTERN = Pattern.compile("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z");
4242

43-
private final SimpleDateFormat dateFormat;
43+
private final DateTimeFormatter dateFormat;
4444

4545
public ElasticsearchAiSearchFilterExpressionConverter() {
46-
this.dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
47-
this.dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
46+
this.dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'").withZone(ZoneOffset.UTC);
4847
}
4948

5049
@Override
@@ -121,15 +120,15 @@ protected void doValue(Filter.Value filterValue, StringBuilder context) {
121120
@Override
122121
protected void doSingleValue(Object value, StringBuilder context) {
123122
if (value instanceof Date date) {
124-
context.append(this.dateFormat.format(date));
123+
context.append(this.dateFormat.format(date.toInstant()));
125124
}
126125
else if (value instanceof String text) {
127126
if (DATE_FORMAT_PATTERN.matcher(text).matches()) {
128127
try {
129-
Date date = this.dateFormat.parse(text);
128+
var date = this.dateFormat.parse(text);
130129
context.append(this.dateFormat.format(date));
131130
}
132-
catch (ParseException e) {
131+
catch (DateTimeParseException e) {
133132
throw new IllegalArgumentException("Invalid date type:" + text, e);
134133
}
135134
}

vector-stores/spring-ai-elasticsearch-store/src/test/java/org/springframework/ai/vectorstore/elasticsearch/ElasticsearchAiSearchFilterExpressionConverterTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.util.Date;
2020
import java.util.List;
21+
import java.util.stream.IntStream;
2122

2223
import org.junit.jupiter.api.Test;
2324

@@ -49,6 +50,18 @@ public void testDate() {
4950
assertThat(vectorExpr).isEqualTo("metadata.activationDate:1970-01-01T00:00:02Z");
5051
}
5152

53+
@Test
54+
public void testDatesConcurrently() {
55+
IntStream.range(0, 10).parallel().forEach(i -> {
56+
String vectorExpr = this.converter.convertExpression(new Filter.Expression(EQ,
57+
new Filter.Key("activationDate"), new Filter.Value(new Date(1704637752148L))));
58+
String vectorExpr2 = this.converter.convertExpression(new Filter.Expression(EQ,
59+
new Filter.Key("activationDate"), new Filter.Value(new Date(1704637753150L))));
60+
assertThat(vectorExpr).isEqualTo("metadata.activationDate:2024-01-07T14:29:12Z");
61+
assertThat(vectorExpr2).isEqualTo("metadata.activationDate:2024-01-07T14:29:13Z");
62+
});
63+
}
64+
5265
@Test
5366
public void testEQ() {
5467
String vectorExpr = this.converter

vector-stores/spring-ai-opensearch-store/src/main/java/org/springframework/ai/vectorstore/opensearch/OpenSearchAiSearchFilterExpressionConverter.java

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@
1616

1717
package org.springframework.ai.vectorstore.opensearch;
1818

19-
import java.text.ParseException;
20-
import java.text.SimpleDateFormat;
19+
import java.time.ZoneOffset;
20+
import java.time.format.DateTimeFormatter;
21+
import java.time.format.DateTimeParseException;
2122
import java.util.Date;
2223
import java.util.List;
23-
import java.util.TimeZone;
2424
import java.util.regex.Pattern;
2525

2626
import org.springframework.ai.vectorstore.filter.Filter;
@@ -38,11 +38,10 @@ public class OpenSearchAiSearchFilterExpressionConverter extends AbstractFilterE
3838

3939
private static final Pattern DATE_FORMAT_PATTERN = Pattern.compile("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z");
4040

41-
private final SimpleDateFormat dateFormat;
41+
private final DateTimeFormatter dateFormat;
4242

4343
public OpenSearchAiSearchFilterExpressionConverter() {
44-
this.dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
45-
this.dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
44+
this.dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'").withZone(ZoneOffset.UTC);
4645
}
4746

4847
@Override
@@ -119,15 +118,15 @@ protected void doValue(Filter.Value filterValue, StringBuilder context) {
119118
@Override
120119
protected void doSingleValue(Object value, StringBuilder context) {
121120
if (value instanceof Date date) {
122-
context.append(this.dateFormat.format(date));
121+
context.append(this.dateFormat.format(date.toInstant()));
123122
}
124123
else if (value instanceof String text) {
125124
if (DATE_FORMAT_PATTERN.matcher(text).matches()) {
126125
try {
127-
Date date = this.dateFormat.parse(text);
126+
var date = this.dateFormat.parse(text);
128127
context.append(this.dateFormat.format(date));
129128
}
130-
catch (ParseException e) {
129+
catch (DateTimeParseException e) {
131130
throw new IllegalArgumentException("Invalid date type:" + text, e);
132131
}
133132
}

0 commit comments

Comments
 (0)