Skip to content

Commit 4490e0a

Browse files
authored
perf: UnitsOfTimeFormatter (#51)
* perf: Units of Time formatter, instead of managing properties * perf: UnitsOfTimeFormatter instead of properties files * perf: UnitsOfTimeFormatter instead of properties files * perf: UnitsOfTimeFormatter instead of properties files * perf: UnitsOfTimeFormatter instead of properties files * perf: UnitsOfTimeFormatter instead of properties files * perf: UnitsOfTimeFormatter instead of properties files * test: support NBSP and empty space for testing .replace("\u00a0"," "); * test: support NBSP and empty space for testing .replace("\u00a0"," "); * chore: private explicit constructor for UnitsOfTimeFormatter
1 parent 6138f20 commit 4490e0a

25 files changed

+139
-144
lines changed

common/src/main/java/io/github/jy95/fds/common/config/DefaultImplementations.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ public static ResourceBundle selectResourceBundle(Locale locale) {
2929
"common",
3030
"daysOfWeek",
3131
"eventTiming",
32-
"quantityComparator",
33-
"unitsOfTime"
32+
"quantityComparator"
3433
);
3534
return ResourceBundle.getBundle(
3635
bundleControl.getBaseName(),

common/src/main/java/io/github/jy95/fds/common/functions/QuantityToString.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import java.math.BigDecimal;
66
import java.util.List;
7+
import java.util.Locale;
78
import java.util.ResourceBundle;
89
import java.util.concurrent.CompletableFuture;
910
import java.util.stream.Collectors;
@@ -42,7 +43,7 @@ public interface QuantityToString<C extends FDSConfig, Q> {
4243
default CompletableFuture<String> convert(ResourceBundle bundle, C config, Q quantity) {
4344
var comparator = comparatorToString(bundle, config, quantity);
4445
var unit = hasUnit(quantity)
45-
? enhancedFromUnitToString(bundle, config, quantity)
46+
? enhancedFromUnitToString(config, quantity)
4647
: CompletableFuture.completedFuture("");
4748
var amount = getValue(quantity).toString();
4849

@@ -73,12 +74,11 @@ default CompletableFuture<String> convert(ResourceBundle bundle, C config, Q qua
7374
/**
7475
* Provides enhanced logic for converting units to a human-readable string.
7576
*
76-
* @param bundle The resource bundle for localization.
7777
* @param config The configuration object for additional logic.
7878
* @param quantity The quantity object.
7979
* @return A CompletableFuture that resolves to the human-readable string for the unit.
8080
*/
81-
CompletableFuture<String> enhancedFromUnitToString(ResourceBundle bundle, C config, Q quantity);
81+
CompletableFuture<String> enhancedFromUnitToString(C config, Q quantity);
8282

8383
/**
8484
* Converts the comparator of a quantity to a human-readable string.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package io.github.jy95.fds.common.functions;
2+
3+
import com.ibm.icu.number.NumberFormatter;
4+
import com.ibm.icu.number.Precision;
5+
import com.ibm.icu.number.UnlocalizedNumberFormatter;
6+
import com.ibm.icu.util.MeasureUnit;
7+
8+
import java.util.Locale;
9+
import java.util.Map;
10+
11+
/**
12+
* Class to format units of time
13+
*/
14+
public final class UnitsOfTimeFormatter {
15+
16+
/**
17+
* Map each code of <a href="https://build.fhir.org/valueset-units-of-time.html">Units of Time</a> to their ICU4J units
18+
*/
19+
private static final Map<String, MeasureUnit> UNIT_MAPPING = Map.ofEntries(
20+
Map.entry("ms", MeasureUnit.MILLISECOND),
21+
Map.entry("s", MeasureUnit.SECOND),
22+
Map.entry("min", MeasureUnit.MINUTE),
23+
Map.entry("h", MeasureUnit.HOUR),
24+
Map.entry("d", MeasureUnit.DAY),
25+
Map.entry("wk", MeasureUnit.WEEK),
26+
Map.entry("mo", MeasureUnit.MONTH),
27+
Map.entry("a", MeasureUnit.YEAR)
28+
);
29+
30+
/**
31+
* Common formatter for all calls to this class
32+
*/
33+
private static final UnlocalizedNumberFormatter formatter = NumberFormatter
34+
.with()
35+
.unitWidth(NumberFormatter.UnitWidth.FULL_NAME);
36+
37+
/**
38+
* No constructor for this class
39+
*/
40+
private UnitsOfTimeFormatter() {}
41+
42+
/**
43+
* Formats a time unit with a count (e.g., "3 heures", "1 minute").
44+
* @param locale The locale expected for the resulting text
45+
* @param unit The <a href="https://build.fhir.org/valueset-units-of-time.html">unit code</a>
46+
* @param count The quantity
47+
* @return A formatted time-unit string with a count
48+
*/
49+
public static String formatWithCount(Locale locale, String unit, Number count) {
50+
return formatter
51+
.locale(locale)
52+
.unit(UNIT_MAPPING.get(unit))
53+
.format(count)
54+
.toString();
55+
}
56+
57+
/**
58+
* Formats a time unit without a count (e.g., "heures", "minute").
59+
* @param locale The locale expected for the resulting text
60+
* @param unit The <a href="https://build.fhir.org/valueset-units-of-time.html">unit code</a>
61+
* @param count The quantity
62+
* @return A formatted time-unit string without a count
63+
*/
64+
public static String formatWithoutCount(Locale locale, String unit, Number count) {
65+
// ICU4j doesn't have a method for extracting only the unit with plural form so a bit of magic here
66+
return formatter
67+
.unit(UNIT_MAPPING.get(unit))
68+
.locale(locale)
69+
.precision(Precision.maxSignificantDigits(1))
70+
.format(count)
71+
.toString()
72+
.substring(2); // 1 because of the number display + 1 because of spacing
73+
}
74+
}

common/src/main/java/io/github/jy95/fds/common/translators/DurationDurationMax.java

-14
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import io.github.jy95.fds.common.config.FDSConfig;
66
import io.github.jy95.fds.common.types.TranslatorTiming;
77

8-
import java.math.BigDecimal;
98
import java.util.ArrayList;
109
import java.util.Locale;
1110
import java.util.ResourceBundle;
@@ -79,19 +78,6 @@ default CompletableFuture<String> convert(D dosage) {
7978
});
8079
}
8180

82-
/**
83-
* Converts the duration quantity and unit into a formatted string.
84-
*
85-
* @param bundle The resourceBundle to use
86-
* @param durationUnit the unit code of duration (e.g., "d", "h").
87-
* @param quantity the quantity of the duration.
88-
* @return the formatted string representing the duration.
89-
*/
90-
default String quantityToString(ResourceBundle bundle, String durationUnit, BigDecimal quantity){
91-
var commonDurationMsg = bundle.getString("withCount." + durationUnit);
92-
return MessageFormat.format(commonDurationMsg, quantity);
93-
}
94-
9581
/**
9682
* Determines if the dosage data contains a valid "duration" value.
9783
*

common/src/main/java/io/github/jy95/fds/common/translators/PeriodPeriodMax.java

-13
Original file line numberDiff line numberDiff line change
@@ -42,19 +42,6 @@ default MessageFormat getPeriodMsg(ResourceBundle bundle, Locale locale) {
4242
return new MessageFormat(msg, locale);
4343
}
4444

45-
/**
46-
* Retrieves the localized unit name for the given period unit and amount.
47-
*
48-
* @param bundle The bundle to extract the key
49-
* @param periodUnit The unit code of the period (e.g., "d", "h").
50-
* @param amount The quantity associated with the period unit.
51-
* @return A localized string representing the unit.
52-
*/
53-
default String getUnit(ResourceBundle bundle, String periodUnit, BigDecimal amount) {
54-
var unitMsg = bundle.getString("withoutCount." + periodUnit);
55-
return MessageFormat.format(unitMsg, amount);
56-
}
57-
5845
/** {@inheritDoc} */
5946
@Override
6047
default CompletableFuture<String> convert(D dosage) {

common/src/main/resources/unitsOfTime_de.properties

-17
This file was deleted.

common/src/main/resources/unitsOfTime_en.properties

-17
This file was deleted.

common/src/main/resources/unitsOfTime_fr.properties

-17
This file was deleted.

common/src/main/resources/unitsOfTime_nl.properties

-17
This file was deleted.

common/src/test/java/io/github/jy95/fds/translators/AbstractDurationDurationMaxTest.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ void testNoDuration(Locale locale) throws ExecutionException, InterruptedExcepti
2727
void testWithDurationOnly(Locale locale) throws ExecutionException, InterruptedException {
2828
var dosage = generateWithDurationOnly();
2929
var dosageUtils = getDosageAPI(locale, DisplayOrder.DURATION_DURATION_MAX);
30-
String result = dosageUtils.asHumanReadableText(dosage).get();
30+
String result = dosageUtils.asHumanReadableText(dosage).get().replace("\u00a0"," ");;
3131
String expectedResult = getExpectedText1(locale);
3232
assertEquals(expectedResult, result);
3333
}
@@ -51,7 +51,7 @@ private String getExpectedText1(Locale locale) {
5151
void testWithDurationMaxOnly(Locale locale) throws ExecutionException, InterruptedException {
5252
var dosage = generateWithDurationMaxOnly();
5353
var dosageUtils = getDosageAPI(locale, DisplayOrder.DURATION_DURATION_MAX);
54-
String result = dosageUtils.asHumanReadableText(dosage).get();
54+
String result = dosageUtils.asHumanReadableText(dosage).get().replace("\u00a0"," ");;
5555
String expectedResult = getExpectedText2(locale);
5656
assertEquals(expectedResult, result);
5757
}
@@ -75,7 +75,7 @@ private String getExpectedText2(Locale locale) {
7575
void testWithBothDuration(Locale locale) throws ExecutionException, InterruptedException {
7676
var dosage = generateWithBothDuration();
7777
var dosageUtils = getDosageAPI(locale, DisplayOrder.DURATION_DURATION_MAX);
78-
String result = dosageUtils.asHumanReadableText(dosage).get();
78+
String result = dosageUtils.asHumanReadableText(dosage).get().replace("\u00a0"," ");;
7979
String expectedResult = getExpectedText3(locale);
8080
assertEquals(expectedResult, result);
8181
}

common/src/test/java/io/github/jy95/fds/translators/AbstractOffsetWhenTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ private String getExpectedText1(Locale locale) {
5151
void testWithWhenAndCount(Locale locale) throws ExecutionException, InterruptedException {
5252
var dosage = generateWithWhenAndCount();
5353
var dosageUtils = getDosageAPI(locale, DisplayOrder.OFFSET_WHEN);
54-
String result = dosageUtils.asHumanReadableText(dosage).get();
54+
String result = dosageUtils.asHumanReadableText(dosage).get().replace("\u00a0"," ");;
5555
String expectedResult = getExpectedText2(locale);
5656
assertEquals(expectedResult, result);
5757
}

r4/src/main/java/io/github/jy95/fds/r4/functions/QuantityToStringR4.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.ibm.icu.text.MessageFormat;
44
import io.github.jy95.fds.common.functions.QuantityToString;
5+
import io.github.jy95.fds.common.functions.UnitsOfTimeFormatter;
56
import io.github.jy95.fds.r4.config.FDSConfigR4;
67
import org.hl7.fhir.r4.model.Quantity;
78

@@ -48,14 +49,13 @@ public BigDecimal getValue(Quantity quantity) {
4849

4950
/** {@inheritDoc} */
5051
@Override
51-
public CompletableFuture<String> enhancedFromUnitToString(ResourceBundle bundle, FDSConfigR4 config, Quantity quantity) {
52+
public CompletableFuture<String> enhancedFromUnitToString(FDSConfigR4 config, Quantity quantity) {
5253
// Duration units are built-in supported
5354
if (quantity.hasSystem() && quantity.hasCode() && TIME_SYSTEMS.contains(quantity.getSystem())) {
5455
return CompletableFuture.supplyAsync(() -> {
5556
String code = quantity.getCode();
5657
BigDecimal amount = quantity.hasValue() ? quantity.getValue() : BigDecimal.ONE;
57-
String message = bundle.getString("withoutCount." + code);
58-
return MessageFormat.format(message, amount);
58+
return UnitsOfTimeFormatter.formatWithoutCount(config.getLocale(), code, amount);
5959
});
6060
}
6161

r4/src/main/java/io/github/jy95/fds/r4/functions/RangeToStringR4.java

-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ public CompletableFuture<String> getUnitText(ResourceBundle bundle, FDSConfigR4
5050
return QuantityToStringR4
5151
.getInstance()
5252
.enhancedFromUnitToString(
53-
bundle,
5453
config,
5554
(hasHigh) ? range.getHigh() : range.getLow()
5655
);

r4/src/main/java/io/github/jy95/fds/r4/functions/RatioToStringR4.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public CompletableFuture<String> convertDenominator(ResourceBundle bundle, FDSCo
8989
if (BigDecimal.ONE.equals(denominatorValue)) {
9090
return QuantityToStringR4
9191
.getInstance()
92-
.enhancedFromUnitToString(bundle, config, denominator);
92+
.enhancedFromUnitToString(config, denominator);
9393
}
9494

9595
return QuantityToStringR4

r4/src/main/java/io/github/jy95/fds/r4/translators/DurationDurationMaxR4.java

+9-7
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package io.github.jy95.fds.r4.translators;
22

33
import com.ibm.icu.text.MessageFormat;
4+
import io.github.jy95.fds.common.functions.UnitsOfTimeFormatter;
45
import io.github.jy95.fds.common.translators.DurationDurationMax;
56
import io.github.jy95.fds.r4.config.FDSConfigR4;
67
import org.hl7.fhir.r4.model.Dosage;
78

9+
import java.util.Locale;
810
import java.util.ResourceBundle;
911

1012
/**
@@ -21,9 +23,9 @@ public class DurationDurationMaxR4 implements DurationDurationMax<FDSConfigR4, D
2123
protected final MessageFormat durationMaxMsg;
2224

2325
/**
24-
* The resource bundle containing localized strings for translation.
26+
* The locale for translation.
2527
*/
26-
private final ResourceBundle bundle;
28+
private final Locale locale;
2729

2830
/**
2931
* Constructor for {@code DurationDurationMaxR4}.
@@ -32,9 +34,9 @@ public class DurationDurationMaxR4 implements DurationDurationMax<FDSConfigR4, D
3234
* @param bundle a {@link java.util.ResourceBundle} object
3335
*/
3436
public DurationDurationMaxR4(FDSConfigR4 config, ResourceBundle bundle) {
35-
this.durationMsg = getDurationMsg(bundle, config.getLocale());
36-
this.durationMaxMsg = getDurationMaxMsg(bundle, config.getLocale());
37-
this.bundle = bundle;
37+
this.locale = config.getLocale();
38+
this.durationMsg = getDurationMsg(bundle, locale);
39+
this.durationMaxMsg = getDurationMaxMsg(bundle, locale);
3840
}
3941

4042
/** {@inheritDoc} */
@@ -70,7 +72,7 @@ public String turnDurationToString(Dosage dosage) {
7072
var durationUnit = repeat.getDurationUnit().toCode();
7173
var durationQuantity = repeat.getDuration();
7274

73-
var durationText = quantityToString(bundle, durationUnit, durationQuantity);
75+
var durationText = UnitsOfTimeFormatter.formatWithCount(locale, durationUnit, durationQuantity);
7476
return durationMsg.format(new Object[]{durationText});
7577
}
7678

@@ -81,7 +83,7 @@ public String turnDurationMaxToString(Dosage dosage) {
8183
var durationUnit = repeat.getDurationUnit().toCode();
8284
var durationQuantity = repeat.getDurationMax();
8385

84-
var durationText = quantityToString(bundle, durationUnit, durationQuantity);
86+
var durationText = UnitsOfTimeFormatter.formatWithCount(locale, durationUnit, durationQuantity);
8587
return durationMaxMsg.format(new Object[]{durationText});
8688
}
8789
}

0 commit comments

Comments
 (0)