Skip to content

Commit

Permalink
Formatting parameters for changeTimeZone function (#16939)
Browse files Browse the repository at this point in the history
* added fix for parsing high precision datetime string

* added ability to pass dateTimeFormat and other formatting params into changeTimeZone, added tests
  • Loading branch information
lucero-v authored Jan 6, 2025
1 parent 3a07ce9 commit f1474fc
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 18 deletions.
1 change: 1 addition & 0 deletions prime-router/src/main/kotlin/common/DateUtilities.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ object DateUtilities {

/** wraps around all the possible variations of a date for finding something that matches */
const val variableDateTimePattern = "[yyyyMMdd]" +
"[yyyyMMddHHmmss.SSSSxx]" +
"[yyyyMMdd[HHmm][ss][.S][Z]]" +
"[yyyy-MM-dd HH:mm:ss.ZZZ]" +
// nano seconds
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import org.hl7.fhir.r4.model.HumanName
import org.hl7.fhir.r4.model.IntegerType
import org.hl7.fhir.r4.model.StringType
import java.time.DateTimeException
import java.time.LocalDate
import java.time.ZoneId
import java.time.format.DateTimeParseException
import java.util.TimeZone

/**
Expand Down Expand Up @@ -109,9 +111,13 @@ object CustomFHIRFunctions : FhirPathFunctions {

CustomFHIRFunctionNames.ChangeTimezone -> {
FunctionDetails(
"changes the timezone of a dateTime, instant, or date resource to the timezone passed in",
"changes the timezone of a dateTime, instant, or date resource to the timezone passed in. " +
"optional params: " +
"dateTimeFormat ('OFFSET', 'LOCAL', 'HIGH_PRECISION_OFFSET', 'DATE_ONLY')(default: 'OFFSET')," +
" convertPositiveDateTimeOffsetToNegative (boolean)(default: false)," +
" useHighPrecisionHeaderDateTimeFormat (boolean)(default: false)",
1,
1
4
)
}

Expand Down Expand Up @@ -446,10 +452,19 @@ object CustomFHIRFunctions : FhirPathFunctions {
throw SchemaException("Must call changeTimezone on a single element")
}

if (parameters == null || parameters[0].size != 1) {
if (parameters == null || parameters.first().isEmpty()) {
throw SchemaException("Must pass a timezone as the parameter")
}

var dateTimeFormat = DateUtilities.DateTimeFormat.OFFSET
if (parameters.size > 1) {
try {
dateTimeFormat = DateUtilities.DateTimeFormat.valueOf(parameters.get(1).first().primitiveValue())
} catch (e: IllegalArgumentException) {
throw SchemaException("Date time format not found.")
}
}

val inputTimeZone = parameters.first().first().primitiveValue()
val timezonePassed = try {
TimeZone.getTimeZone(ZoneId.of(inputTimeZone))
Expand All @@ -462,27 +477,24 @@ object CustomFHIRFunctions : FhirPathFunctions {
}

return if (focus[0] is StringType) {
if (focus[0].toString().length <= 8) { // we don't want to convert Date-only strings
return mutableListOf(StringType(focus[0].toString()))
val inputDate = try {
DateUtilities.parseDate((focus[0].toString()))
} catch (e: DateTimeParseException) {
throw SchemaException("Error trying to change time zone: " + e.message)
}

// TODO: find a way to pass in these values from receiver settings

val dateTimeFormat = null
val convertPositiveDateTimeOffsetToNegative = null
val useHighPrecisionHeaderDateTimeFormat = null
if (inputDate is LocalDate) {
return mutableListOf(StringType(focus[0].toString()))
}

val formattedDate = DateUtilities.formatDateForReceiver(
DateUtilities.parseDate((focus[0].toString())),
inputDate,
ZoneId.of(inputTimeZone),
dateTimeFormat ?: DateUtilities.DateTimeFormat.OFFSET,
convertPositiveDateTimeOffsetToNegative ?: false,
useHighPrecisionHeaderDateTimeFormat ?: false
)

mutableListOf(
StringType(formattedDate)
dateTimeFormat,
parameters.getOrNull(2)?.first()?.primitiveValue()?.toBoolean() ?: false,
parameters.getOrNull(3)?.first()?.primitiveValue()?.toBoolean() ?: false
)
mutableListOf(StringType(formattedDate))
} else {
val inputDate = focus[0] as? BaseDateTimeType ?: throw SchemaException(
"Must call changeTimezone on a dateTime, instant, or date; " +
Expand Down
19 changes: 19 additions & 0 deletions prime-router/src/test/kotlin/common/DateUtilitiesTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@ class DateUtilitiesTests {
"12/1/1900" to "19001201000000",
"2/3/02" to "20020203000000",
"2/3/02 8:00" to "20020203080000",
"20241220194528.4230+0000" to "20241220194528"
).forEach { (input, expected) ->
val parsed = DateUtilities.parseDate(input)
assertThat(
Expand All @@ -417,6 +418,24 @@ class DateUtilitiesTests {
)
).isEqualTo(expected)
}

// test high precision offset
mapOf(
"1975-08-01T11:39:00Z" to "19750801113900.0000+0000",
"2022-04-29T15:43:02.307Z" to "20220429154302.3070+0000",
"20241220194528.4230+0000" to "20241220194528.4230+0000"
).forEach { (input, expected) ->
val parsed = DateUtilities.parseDate(input)
assertThat(
DateUtilities.formatDateForReceiver(
parsed,
DateUtilities.utcZone,
DateUtilities.DateTimeFormat.HIGH_PRECISION_OFFSET,
false,
false
)
).isEqualTo(expected)
}
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,120 @@ class CustomFHIRFunctionsTests {
}.hasClass(SchemaException::class.java)
}

@Test
fun `test changeTimezone with date as string - success`() {
val date = StringType("20241220194528.4230+0000")
val timezone = StringType("UTC")
val dateTimeFormat = StringType("HIGH_PRECISION_OFFSET")
val convertToNegative = StringType("true")
val useHighPrecision = StringType("false")

// test format
var outputDate = CustomFHIRFunctions.changeTimezone(
mutableListOf(date),
mutableListOf(
mutableListOf(timezone), mutableListOf(dateTimeFormat), mutableListOf(convertToNegative),
mutableListOf(useHighPrecision)
)
)
assertThat(outputDate[0]).isInstanceOf(StringType::class.java)
assertThat(outputDate[0].primitiveValue()).isEqualTo("20241220194528.4230-0000")

// test timezone change with offset format
timezone.value = "America/Phoenix"
dateTimeFormat.value = "OFFSET"
convertToNegative.value = "false"
useHighPrecision.value = "false"

outputDate = CustomFHIRFunctions.changeTimezone(
mutableListOf(date),
mutableListOf(
mutableListOf(timezone), mutableListOf(dateTimeFormat), mutableListOf(convertToNegative),
mutableListOf(useHighPrecision)
)
)
assertThat(outputDate[0]).isInstanceOf(StringType::class.java)
assertThat(outputDate[0].primitiveValue()).isEqualTo("20241220124528-0700")

// test different format for input date
val date2 = StringType("2021-08-09T08:52:34-04:00")
timezone.value = "America/Phoenix"
dateTimeFormat.value = "LOCAL"
convertToNegative.value = "false"
useHighPrecision.value = "false"

outputDate = CustomFHIRFunctions.changeTimezone(
mutableListOf(date2),
mutableListOf(
mutableListOf(timezone), mutableListOf(dateTimeFormat), mutableListOf(convertToNegative),
mutableListOf(useHighPrecision)
)
)
assertThat(outputDate[0]).isInstanceOf(StringType::class.java)
assertThat(outputDate[0].primitiveValue()).isEqualTo("20210809055234")

// test date without time should return same date string
val date3 = StringType("2021-08-09")
timezone.value = "America/Phoenix"
dateTimeFormat.value = "OFFSET"
convertToNegative.value = "false"
useHighPrecision.value = "false"

outputDate = CustomFHIRFunctions.changeTimezone(
mutableListOf(date3),
mutableListOf(
mutableListOf(timezone), mutableListOf(dateTimeFormat), mutableListOf(convertToNegative),
mutableListOf(useHighPrecision)
)
)
assertThat(outputDate[0]).isInstanceOf(StringType::class.java)
assertThat(outputDate[0].primitiveValue()).isEqualTo("2021-08-09")

// test timezone change with required param only
timezone.value = "America/Phoenix"

outputDate = CustomFHIRFunctions.changeTimezone(
mutableListOf(date),
mutableListOf(mutableListOf(timezone))
)
assertThat(outputDate[0]).isInstanceOf(StringType::class.java)
assertThat(outputDate[0].primitiveValue()).isEqualTo("20241220124528-0700")
}

@Test
fun `test changeTimezone with date as string - exception`() {
val date = StringType("20241220194528.4230+0000")
val timezone = StringType("UTC")
val dateTimeFormat = StringType("HIGH_PRECISION_OFFSET")
val convertToNegative = StringType("true")
val useHighPrecision = StringType("false")

assertFailure {
dateTimeFormat.value = "HIGH_PRECISION_OFFS"
// test invalid dateTime format
CustomFHIRFunctions.changeTimezone(
mutableListOf(date),
mutableListOf(
mutableListOf(timezone), mutableListOf(dateTimeFormat), mutableListOf(convertToNegative),
mutableListOf(useHighPrecision)
)
)
}.hasClass(SchemaException::class.java)

assertFailure {
dateTimeFormat.value = "HIGH_PRECISION_OFFSET"
date.value = "2021-08-09T"
// test invalid dateTime string input
CustomFHIRFunctions.changeTimezone(
mutableListOf(date),
mutableListOf(
mutableListOf(timezone), mutableListOf(dateTimeFormat), mutableListOf(convertToNegative),
mutableListOf(useHighPrecision)
)
)
}.hasClass(SchemaException::class.java)
}

@Test
fun `test deidentifies a human name`() {
val name = HumanName()
Expand Down

0 comments on commit f1474fc

Please sign in to comment.