Skip to content

Commit

Permalink
perf: UnitsOfTimeFormatter (#51)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
jy95 authored Feb 9, 2025
1 parent 6138f20 commit 4490e0a
Show file tree
Hide file tree
Showing 25 changed files with 139 additions and 144 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ public static ResourceBundle selectResourceBundle(Locale locale) {
"common",
"daysOfWeek",
"eventTiming",
"quantityComparator",
"unitsOfTime"
"quantityComparator"
);
return ResourceBundle.getBundle(
bundleControl.getBaseName(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import java.math.BigDecimal;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -42,7 +43,7 @@ public interface QuantityToString<C extends FDSConfig, Q> {
default CompletableFuture<String> convert(ResourceBundle bundle, C config, Q quantity) {
var comparator = comparatorToString(bundle, config, quantity);
var unit = hasUnit(quantity)
? enhancedFromUnitToString(bundle, config, quantity)
? enhancedFromUnitToString(config, quantity)
: CompletableFuture.completedFuture("");
var amount = getValue(quantity).toString();

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

/**
* Converts the comparator of a quantity to a human-readable string.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package io.github.jy95.fds.common.functions;

import com.ibm.icu.number.NumberFormatter;
import com.ibm.icu.number.Precision;
import com.ibm.icu.number.UnlocalizedNumberFormatter;
import com.ibm.icu.util.MeasureUnit;

import java.util.Locale;
import java.util.Map;

/**
* Class to format units of time
*/
public final class UnitsOfTimeFormatter {

/**
* Map each code of <a href="https://build.fhir.org/valueset-units-of-time.html">Units of Time</a> to their ICU4J units
*/
private static final Map<String, MeasureUnit> UNIT_MAPPING = Map.ofEntries(
Map.entry("ms", MeasureUnit.MILLISECOND),
Map.entry("s", MeasureUnit.SECOND),
Map.entry("min", MeasureUnit.MINUTE),
Map.entry("h", MeasureUnit.HOUR),
Map.entry("d", MeasureUnit.DAY),
Map.entry("wk", MeasureUnit.WEEK),
Map.entry("mo", MeasureUnit.MONTH),
Map.entry("a", MeasureUnit.YEAR)
);

/**
* Common formatter for all calls to this class
*/
private static final UnlocalizedNumberFormatter formatter = NumberFormatter
.with()
.unitWidth(NumberFormatter.UnitWidth.FULL_NAME);

/**
* No constructor for this class
*/
private UnitsOfTimeFormatter() {}

/**
* Formats a time unit with a count (e.g., "3 heures", "1 minute").
* @param locale The locale expected for the resulting text
* @param unit The <a href="https://build.fhir.org/valueset-units-of-time.html">unit code</a>
* @param count The quantity
* @return A formatted time-unit string with a count
*/
public static String formatWithCount(Locale locale, String unit, Number count) {
return formatter
.locale(locale)
.unit(UNIT_MAPPING.get(unit))
.format(count)
.toString();
}

/**
* Formats a time unit without a count (e.g., "heures", "minute").
* @param locale The locale expected for the resulting text
* @param unit The <a href="https://build.fhir.org/valueset-units-of-time.html">unit code</a>
* @param count The quantity
* @return A formatted time-unit string without a count
*/
public static String formatWithoutCount(Locale locale, String unit, Number count) {
// ICU4j doesn't have a method for extracting only the unit with plural form so a bit of magic here
return formatter
.unit(UNIT_MAPPING.get(unit))
.locale(locale)
.precision(Precision.maxSignificantDigits(1))
.format(count)
.toString()
.substring(2); // 1 because of the number display + 1 because of spacing
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import io.github.jy95.fds.common.config.FDSConfig;
import io.github.jy95.fds.common.types.TranslatorTiming;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Locale;
import java.util.ResourceBundle;
Expand Down Expand Up @@ -79,19 +78,6 @@ default CompletableFuture<String> convert(D dosage) {
});
}

/**
* Converts the duration quantity and unit into a formatted string.
*
* @param bundle The resourceBundle to use
* @param durationUnit the unit code of duration (e.g., "d", "h").
* @param quantity the quantity of the duration.
* @return the formatted string representing the duration.
*/
default String quantityToString(ResourceBundle bundle, String durationUnit, BigDecimal quantity){
var commonDurationMsg = bundle.getString("withCount." + durationUnit);
return MessageFormat.format(commonDurationMsg, quantity);
}

/**
* Determines if the dosage data contains a valid "duration" value.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,6 @@ default MessageFormat getPeriodMsg(ResourceBundle bundle, Locale locale) {
return new MessageFormat(msg, locale);
}

/**
* Retrieves the localized unit name for the given period unit and amount.
*
* @param bundle The bundle to extract the key
* @param periodUnit The unit code of the period (e.g., "d", "h").
* @param amount The quantity associated with the period unit.
* @return A localized string representing the unit.
*/
default String getUnit(ResourceBundle bundle, String periodUnit, BigDecimal amount) {
var unitMsg = bundle.getString("withoutCount." + periodUnit);
return MessageFormat.format(unitMsg, amount);
}

/** {@inheritDoc} */
@Override
default CompletableFuture<String> convert(D dosage) {
Expand Down
17 changes: 0 additions & 17 deletions common/src/main/resources/unitsOfTime_de.properties

This file was deleted.

17 changes: 0 additions & 17 deletions common/src/main/resources/unitsOfTime_en.properties

This file was deleted.

17 changes: 0 additions & 17 deletions common/src/main/resources/unitsOfTime_fr.properties

This file was deleted.

17 changes: 0 additions & 17 deletions common/src/main/resources/unitsOfTime_nl.properties

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ void testNoDuration(Locale locale) throws ExecutionException, InterruptedExcepti
void testWithDurationOnly(Locale locale) throws ExecutionException, InterruptedException {
var dosage = generateWithDurationOnly();
var dosageUtils = getDosageAPI(locale, DisplayOrder.DURATION_DURATION_MAX);
String result = dosageUtils.asHumanReadableText(dosage).get();
String result = dosageUtils.asHumanReadableText(dosage).get().replace("\u00a0"," ");;
String expectedResult = getExpectedText1(locale);
assertEquals(expectedResult, result);
}
Expand All @@ -51,7 +51,7 @@ private String getExpectedText1(Locale locale) {
void testWithDurationMaxOnly(Locale locale) throws ExecutionException, InterruptedException {
var dosage = generateWithDurationMaxOnly();
var dosageUtils = getDosageAPI(locale, DisplayOrder.DURATION_DURATION_MAX);
String result = dosageUtils.asHumanReadableText(dosage).get();
String result = dosageUtils.asHumanReadableText(dosage).get().replace("\u00a0"," ");;
String expectedResult = getExpectedText2(locale);
assertEquals(expectedResult, result);
}
Expand All @@ -75,7 +75,7 @@ private String getExpectedText2(Locale locale) {
void testWithBothDuration(Locale locale) throws ExecutionException, InterruptedException {
var dosage = generateWithBothDuration();
var dosageUtils = getDosageAPI(locale, DisplayOrder.DURATION_DURATION_MAX);
String result = dosageUtils.asHumanReadableText(dosage).get();
String result = dosageUtils.asHumanReadableText(dosage).get().replace("\u00a0"," ");;
String expectedResult = getExpectedText3(locale);
assertEquals(expectedResult, result);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ private String getExpectedText1(Locale locale) {
void testWithWhenAndCount(Locale locale) throws ExecutionException, InterruptedException {
var dosage = generateWithWhenAndCount();
var dosageUtils = getDosageAPI(locale, DisplayOrder.OFFSET_WHEN);
String result = dosageUtils.asHumanReadableText(dosage).get();
String result = dosageUtils.asHumanReadableText(dosage).get().replace("\u00a0"," ");;
String expectedResult = getExpectedText2(locale);
assertEquals(expectedResult, result);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.ibm.icu.text.MessageFormat;
import io.github.jy95.fds.common.functions.QuantityToString;
import io.github.jy95.fds.common.functions.UnitsOfTimeFormatter;
import io.github.jy95.fds.r4.config.FDSConfigR4;
import org.hl7.fhir.r4.model.Quantity;

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

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ public CompletableFuture<String> getUnitText(ResourceBundle bundle, FDSConfigR4
return QuantityToStringR4
.getInstance()
.enhancedFromUnitToString(
bundle,
config,
(hasHigh) ? range.getHigh() : range.getLow()
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public CompletableFuture<String> convertDenominator(ResourceBundle bundle, FDSCo
if (BigDecimal.ONE.equals(denominatorValue)) {
return QuantityToStringR4
.getInstance()
.enhancedFromUnitToString(bundle, config, denominator);
.enhancedFromUnitToString(config, denominator);
}

return QuantityToStringR4
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package io.github.jy95.fds.r4.translators;

import com.ibm.icu.text.MessageFormat;
import io.github.jy95.fds.common.functions.UnitsOfTimeFormatter;
import io.github.jy95.fds.common.translators.DurationDurationMax;
import io.github.jy95.fds.r4.config.FDSConfigR4;
import org.hl7.fhir.r4.model.Dosage;

import java.util.Locale;
import java.util.ResourceBundle;

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

/**
* The resource bundle containing localized strings for translation.
* The locale for translation.
*/
private final ResourceBundle bundle;
private final Locale locale;

/**
* Constructor for {@code DurationDurationMaxR4}.
Expand All @@ -32,9 +34,9 @@ public class DurationDurationMaxR4 implements DurationDurationMax<FDSConfigR4, D
* @param bundle a {@link java.util.ResourceBundle} object
*/
public DurationDurationMaxR4(FDSConfigR4 config, ResourceBundle bundle) {
this.durationMsg = getDurationMsg(bundle, config.getLocale());
this.durationMaxMsg = getDurationMaxMsg(bundle, config.getLocale());
this.bundle = bundle;
this.locale = config.getLocale();
this.durationMsg = getDurationMsg(bundle, locale);
this.durationMaxMsg = getDurationMaxMsg(bundle, locale);
}

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

var durationText = quantityToString(bundle, durationUnit, durationQuantity);
var durationText = UnitsOfTimeFormatter.formatWithCount(locale, durationUnit, durationQuantity);
return durationMsg.format(new Object[]{durationText});
}

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

var durationText = quantityToString(bundle, durationUnit, durationQuantity);
var durationText = UnitsOfTimeFormatter.formatWithCount(locale, durationUnit, durationQuantity);
return durationMaxMsg.format(new Object[]{durationText});
}
}
Loading

0 comments on commit 4490e0a

Please sign in to comment.