Skip to content

Commit 446435b

Browse files
committed
implement date time formatter in NumberDisplay and StringDeformatters
1 parent cf6fe80 commit 446435b

File tree

9 files changed

+209
-46
lines changed

9 files changed

+209
-46
lines changed

query/display-number/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,10 @@
1818
<artifactId>topper-query-simple</artifactId>
1919
<version>${project.version}</version>
2020
</dependency>
21+
<dependency>
22+
<groupId>me.hsgamer</groupId>
23+
<artifactId>topper-value-time-format</artifactId>
24+
<version>${project.version}</version>
25+
</dependency>
2126
</dependencies>
2227
</project>

query/display-number/src/main/java/me/hsgamer/topper/query/display/number/NumberDisplay.java

Lines changed: 25 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
package me.hsgamer.topper.query.display.number;
22

33
import me.hsgamer.topper.query.simple.SimpleQueryDisplay;
4+
import me.hsgamer.topper.value.timeformat.DateTimeFormatters;
5+
import me.hsgamer.topper.value.timeformat.DurationTimeFormatters;
46
import org.jetbrains.annotations.NotNull;
57
import org.jetbrains.annotations.Nullable;
68

7-
import java.lang.reflect.Method;
89
import java.math.RoundingMode;
910
import java.text.DecimalFormat;
1011
import java.text.DecimalFormatSymbols;
@@ -20,35 +21,10 @@
2021
import java.util.stream.Collectors;
2122

2223
public abstract class NumberDisplay<K, V extends Number> implements SimpleQueryDisplay<K, V> {
23-
private static final Method FORMAT_DURATION_METHOD;
2424
private static final Pattern VALUE_PLACEHOLDER_PATTERN = Pattern.compile("\\{value(?:_(.*))?}");
2525
private static final String FORMAT_QUERY_DECIMAL_FORMAT_PREFIX = "decimal:";
2626
private static final String FORMAT_QUERY_TIME_FORMAT_PREFIX = "time:";
2727

28-
static {
29-
Class<?> clazz;
30-
try {
31-
clazz = Class.forName("org.apache.commons.lang3.time.DurationFormatUtils");
32-
} catch (ClassNotFoundException e) {
33-
try {
34-
clazz = Class.forName("org.apache.commons.lang.time.DurationFormatUtils");
35-
} catch (ClassNotFoundException ex) {
36-
clazz = null;
37-
}
38-
}
39-
40-
Method method = null;
41-
if (clazz != null) {
42-
try {
43-
method = clazz.getMethod("formatDuration", long.class, String.class);
44-
} catch (NoSuchMethodException e) {
45-
// Method not found, will return null
46-
}
47-
}
48-
49-
FORMAT_DURATION_METHOD = method;
50-
}
51-
5228
private final String line;
5329
private final String displayNullValue;
5430

@@ -129,44 +105,48 @@ private static Map<String, String> getSettings(String query) {
129105
if (formatQuery.startsWith(FORMAT_QUERY_TIME_FORMAT_PREFIX)) {
130106
Map<String, String> settings = getSettings(formatQuery.substring(FORMAT_QUERY_TIME_FORMAT_PREFIX.length()));
131107

132-
String pattern = Optional.ofNullable(settings.get("pattern")).orElse("HH:mm:ss");
133-
String type = Optional.ofNullable(settings.get("type")).orElse("duration");
134108
long time = value.longValue();
135-
String unitString = Optional.ofNullable(settings.get("unit")).orElse("seconds");
136-
TimeUnit unit;
109+
String unitString = Optional.ofNullable(settings.get("unit")).orElse("ticks");
110+
long millis;
137111
if (unitString.equalsIgnoreCase("ticks")) {
138-
unit = TimeUnit.MILLISECONDS;
139-
time *= 50;
112+
millis = time * 50;
140113
} else {
141114
try {
142-
unit = TimeUnit.valueOf(unitString.toUpperCase());
115+
TimeUnit unit = TimeUnit.valueOf(unitString.toUpperCase());
116+
millis = unit.toMillis(time);
143117
} catch (IllegalArgumentException e) {
144118
return "INVALID_UNIT";
145119
}
146120
}
147-
long millis = unit.toMillis(time);
148121

122+
String type = Optional.ofNullable(settings.get("type")).orElse("duration");
123+
Optional<String> patternOptional = Optional.ofNullable(settings.get("pattern"));
149124
if (type.equalsIgnoreCase("time")) {
150-
DateTimeFormatter formatter;
151-
try {
152-
formatter = DateTimeFormatter.ofPattern(pattern);
153-
} catch (IllegalArgumentException e) {
125+
String pattern = patternOptional.orElse("RFC_1123_DATE_TIME");
126+
Optional<DateTimeFormatter> formatterOptional = DateTimeFormatters.getFormatter(pattern);
127+
if (!formatterOptional.isPresent()) {
154128
return "INVALID_FORMAT";
155129
}
130+
DateTimeFormatter formatter = formatterOptional.get();
131+
156132
Instant date = Instant.ofEpochMilli(millis);
157133
try {
158134
return formatter.format(date);
159135
} catch (IllegalArgumentException e) {
160136
return "CANNOT_FORMAT";
161137
}
162138
} else if (type.equalsIgnoreCase("duration")) {
163-
if (FORMAT_DURATION_METHOD == null) {
164-
return "UNSUPPORTED_DURATION_FORMAT";
165-
}
166-
try {
167-
return (String) FORMAT_DURATION_METHOD.invoke(null, millis, pattern);
168-
} catch (Exception e) {
169-
return "CANNOT_FORMAT";
139+
String pattern = patternOptional.orElse("default");
140+
if (pattern.equalsIgnoreCase("default")) {
141+
return DurationTimeFormatters.formatDuration(millis, "HH:mm:ss");
142+
} else if (pattern.equalsIgnoreCase("word")) {
143+
return DurationTimeFormatters.formatDurationWords(millis, true, true);
144+
} else if (pattern.equalsIgnoreCase("short")) {
145+
return DurationTimeFormatters.formatDuration(millis, "H:mm:ss");
146+
} else if (pattern.equalsIgnoreCase("short-word")) {
147+
return DurationTimeFormatters.formatDuration(millis, "d'd 'H'h 'm'm 's's'");
148+
} else {
149+
return DurationTimeFormatters.formatDuration(millis, pattern);
170150
}
171151
}
172152
}

value/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
<modules>
1515
<module>core</module>
1616
<module>string</module>
17+
<module>time-format</module>
1718
</modules>
1819

1920
<name>Topper Value</name>

value/string/pom.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,12 @@
99

1010
<artifactId>topper-value-string</artifactId>
1111
<name>Topper Value String</name>
12+
13+
<dependencies>
14+
<dependency>
15+
<groupId>me.hsgamer</groupId>
16+
<artifactId>topper-value-time-format</artifactId>
17+
<version>${project.version}</version>
18+
</dependency>
19+
</dependencies>
1220
</project>

value/string/src/main/java/me/hsgamer/topper/value/string/StringDeformatters.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ public static NumberStringDeformatter numberStringDeformatter(Map<String, Object
1616
return new NumberStringDeformatter(decimalSeparator);
1717
}
1818

19+
public static TimeStringDeformatter timeStringDeformatter(Map<String, Object> map) {
20+
String timeFormat = Optional.ofNullable(map.get("time-format"))
21+
.map(Object::toString)
22+
.orElse("HH:mm:ss");
23+
return new TimeStringDeformatter(timeFormat);
24+
}
25+
1926
public static UnaryOperator<String> deformatterOrIdentity(Map<String, Object> map) {
2027
boolean needDeformat = Optional.ofNullable(map.get("formatted"))
2128
.map(Object::toString)
@@ -51,6 +58,8 @@ public static UnaryOperator<String> deformatterOrIdentity(Map<String, Object> ma
5158
switch (type) {
5259
case NUMBER:
5360
return numberStringDeformatter(deformatterMap);
61+
case TIME:
62+
return timeStringDeformatter(deformatterMap);
5463
default:
5564
return UnaryOperator.identity();
5665
}
@@ -59,6 +68,5 @@ public static UnaryOperator<String> deformatterOrIdentity(Map<String, Object> ma
5968
private enum Type {
6069
NUMBER,
6170
TIME,
62-
DURATION,
6371
}
6472
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package me.hsgamer.topper.value.string;
2+
3+
import me.hsgamer.topper.value.timeformat.DateTimeFormatters;
4+
5+
import java.time.format.DateTimeFormatter;
6+
import java.time.temporal.ChronoField;
7+
import java.util.function.UnaryOperator;
8+
9+
public class TimeStringDeformatter implements UnaryOperator<String> {
10+
private final DateTimeFormatter formatter;
11+
12+
public TimeStringDeformatter(DateTimeFormatter formatter) {
13+
this.formatter = formatter;
14+
}
15+
16+
public TimeStringDeformatter(String pattern) {
17+
this(DateTimeFormatters.getFormatter(pattern).orElse(null));
18+
}
19+
20+
@Override
21+
public String apply(String string) {
22+
if (formatter == null) return "NO FORMATTER";
23+
return Long.toString(formatter.parse(string).getLong(ChronoField.INSTANT_SECONDS));
24+
}
25+
}

value/time-format/pom.xml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xmlns="http://maven.apache.org/POM/4.0.0"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
<parent>
7+
<groupId>me.hsgamer</groupId>
8+
<artifactId>topper-value</artifactId>
9+
<version>4.0.0-SNAPSHOT</version>
10+
</parent>
11+
12+
<artifactId>topper-value-time-format</artifactId>
13+
<name>Topper Value Time Format</name>
14+
</project>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package me.hsgamer.topper.value.timeformat;
2+
3+
import java.lang.reflect.Field;
4+
import java.lang.reflect.Modifier;
5+
import java.time.format.DateTimeFormatter;
6+
import java.util.Map;
7+
import java.util.Optional;
8+
import java.util.concurrent.ConcurrentHashMap;
9+
10+
public final class DateTimeFormatters {
11+
private static final Map<String, DateTimeFormatter> FORMATTER_MAP = new ConcurrentHashMap<>();
12+
13+
private DateTimeFormatters() {
14+
// Prevent instantiation
15+
}
16+
17+
public static Optional<DateTimeFormatter> getFormatter(String name) {
18+
if (name == null || name.isEmpty()) {
19+
return Optional.empty();
20+
}
21+
DateTimeFormatter formatter = FORMATTER_MAP.get(name);
22+
if (formatter != null) {
23+
return Optional.of(formatter);
24+
}
25+
26+
Class<DateTimeFormatter> dateTimeFormatterClass = DateTimeFormatter.class;
27+
Field field;
28+
try {
29+
field = dateTimeFormatterClass.getDeclaredField(name);
30+
} catch (NoSuchFieldException e) {
31+
field = null;
32+
}
33+
if (field != null && Modifier.isPublic(field.getModifiers()) && Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers()) && field.getType() == DateTimeFormatter.class) {
34+
try {
35+
formatter = (DateTimeFormatter) field.get(null);
36+
FORMATTER_MAP.put(name, formatter);
37+
return Optional.of(formatter);
38+
} catch (IllegalAccessException e) {
39+
// Ignore and try to create a new formatter
40+
}
41+
}
42+
43+
try {
44+
formatter = DateTimeFormatter.ofPattern(name);
45+
FORMATTER_MAP.put(name, formatter);
46+
return Optional.of(formatter);
47+
} catch (IllegalArgumentException e) {
48+
return Optional.empty();
49+
}
50+
}
51+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package me.hsgamer.topper.value.timeformat;
2+
3+
import java.lang.reflect.Method;
4+
5+
public final class DurationTimeFormatters {
6+
private static final Method FORMAT_DURATION_METHOD;
7+
private static final Method FORMAT_DURATION_WORDS_METHOD;
8+
9+
static {
10+
Class<?> clazz;
11+
try {
12+
clazz = Class.forName("org.apache.commons.lang3.time.DurationFormatUtils");
13+
} catch (ClassNotFoundException e) {
14+
try {
15+
clazz = Class.forName("org.apache.commons.lang.time.DurationFormatUtils");
16+
} catch (ClassNotFoundException ex) {
17+
clazz = null;
18+
}
19+
}
20+
21+
Method method = null;
22+
if (clazz != null) {
23+
try {
24+
method = clazz.getMethod("formatDuration", long.class, String.class);
25+
} catch (NoSuchMethodException e) {
26+
// Method not found, will return null
27+
}
28+
}
29+
30+
FORMAT_DURATION_METHOD = method;
31+
32+
Method wordsMethod = null;
33+
if (clazz != null) {
34+
try {
35+
wordsMethod = clazz.getMethod("formatDurationWords", long.class, boolean.class, boolean.class);
36+
} catch (NoSuchMethodException e) {
37+
// Method not found, will return null
38+
}
39+
}
40+
41+
FORMAT_DURATION_WORDS_METHOD = wordsMethod;
42+
}
43+
44+
private DurationTimeFormatters() {
45+
// Prevent instantiation
46+
}
47+
48+
public static String formatDuration(long durationMillis, String format) {
49+
if (FORMAT_DURATION_METHOD != null) {
50+
try {
51+
return (String) FORMAT_DURATION_METHOD.invoke(null, durationMillis, format);
52+
} catch (Exception e) {
53+
return "INVALID FORMAT";
54+
}
55+
} else {
56+
return "NOT SUPPORTED";
57+
}
58+
}
59+
60+
public static String formatDurationWords(long durationMillis, boolean suppressLeadingZeroElements, boolean suppressTrailingZeroElements) {
61+
if (FORMAT_DURATION_WORDS_METHOD != null) {
62+
try {
63+
return (String) FORMAT_DURATION_WORDS_METHOD.invoke(null, durationMillis, suppressLeadingZeroElements, suppressTrailingZeroElements);
64+
} catch (Exception e) {
65+
return "INVALID FORMAT";
66+
}
67+
} else {
68+
return "NOT SUPPORTED";
69+
}
70+
}
71+
}

0 commit comments

Comments
 (0)