Skip to content

Commit

Permalink
Add support for time zone (#222)
Browse files Browse the repository at this point in the history
* Add support for time zone

* Remove time zone dependent tests for now
  • Loading branch information
BoyaWu10 authored Mar 26, 2021
1 parent 71a7ead commit 3ff7e83
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using Microsoft.Health.Fhir.Liquid.Converter.Exceptions;
using Microsoft.Health.Fhir.Liquid.Converter.Models;
using Xunit;

namespace Microsoft.Health.Fhir.Liquid.Converter.UnitTests.FilterTests
Expand All @@ -24,13 +25,34 @@ public static IEnumerable<object[]> GetValidDataForAddHyphensDate()

public static IEnumerable<object[]> GetValidDataForFormatAsDateTime()
{
yield return new object[] { null, null };
yield return new object[] { string.Empty, string.Empty };
yield return new object[] { @"2001", @"2001" };
yield return new object[] { @"200101", @"2001-01" };
yield return new object[] { @"20050110045253", @"2005-01-10T04:52:53Z" };
yield return new object[] { @"20110103143428-0800", @"2011-01-03T14:34:28-08:00" };
yield return new object[] { @"19701231115959+0600", @"1970-12-31T11:59:59+06:00" };
// TimeZoneHandling does not affect dateTime without time
yield return new object[] { null, "preserve", null };
yield return new object[] { null, "utc", null };
yield return new object[] { null, "local", null };
yield return new object[] { string.Empty, "preserve", string.Empty };
yield return new object[] { string.Empty, "utc", string.Empty };
yield return new object[] { string.Empty, "local", string.Empty };
yield return new object[] { @"2001", "preserve", @"2001" };
yield return new object[] { @"2001", "utc", @"2001" };
yield return new object[] { @"2001", "local", @"2001" };
yield return new object[] { @"200101", "preserve", @"2001-01" };
yield return new object[] { @"200101", "utc", @"2001-01" };
yield return new object[] { @"200101", "local", @"2001-01" };

// If no time zone provided, it is treated as local
yield return new object[] { @"20050110045253", "preserve", @"2005-01-10T04:52:53" };
yield return new object[] { @"20050110045253", "local", @"2005-01-10T04:52:53" };

// If time zone provided, it should be formatted according to TimeZoneHandling
yield return new object[] { @"20110103143428-0800", "preserve", @"2011-01-03T14:34:28-08:00" };
yield return new object[] { @"20110103143428-0800", "utc", @"2011-01-03T22:34:28Z" };
yield return new object[] { @"19701231115959+0600", "preserve", @"1970-12-31T11:59:59+06:00" };
yield return new object[] { @"19701231115959+0600", "utc", @"1970-12-31T05:59:59Z" };

// Skip this test in pipeline, as the local time zone is different
// yield return new object[] { @"20050110045253", "utc", @"2005-01-09T20:52:53Z" };
// yield return new object[] { @"20110103143428-0800", "local", @"2011-01-04T06:34:28+08:00" };
// yield return new object[] { @"19701231115959+0600", "local", @"1970-12-31T13:59:59+08:00" };
}

public static IEnumerable<object[]> GetInvalidDataForAddHyphensDate()
Expand Down Expand Up @@ -61,34 +83,51 @@ public static IEnumerable<object[]> GetInvalidDataForFormatAsDateTime()
yield return new object[] { @"20200101101080" };
}

public static IEnumerable<object[]> GetInvalidTimeZoneHandling()
{
yield return new object[] { @"20050110045253", null };
yield return new object[] { @"20110103143428-0800", string.Empty };
yield return new object[] { @"19701231115959+0600", "abc" };
}

[Theory]
[MemberData(nameof(GetValidDataForAddHyphensDate))]
public void GivenAnHl7v2Date_WhenAddHyphensDate_ConvertedDateShouldBeReturned(string input, string expected)
public void GivenADate_WhenAddHyphensDate_ConvertedDateShouldBeReturned(string input, string expected)
{
var result = Filters.AddHyphensDate(input);
Assert.Equal(expected, result);
}

[Theory]
[MemberData(nameof(GetValidDataForFormatAsDateTime))]
public void GivenAnHl7v2DateTime_WhenFormatAsDateTime_ConvertedDateShouldBeReturned(string input, string expected)
public void GivenADateTime_WhenFormatAsDateTime_ConvertedDateShouldBeReturned(string input, string timeZoneHandling, string expected)
{
var result = Filters.FormatAsDateTime(input);
var result = Filters.FormatAsDateTime(input, timeZoneHandling);
Assert.Equal(expected, result);
}

[Theory]
[MemberData(nameof(GetInvalidDataForAddHyphensDate))]
public void GivenAnInvalidHl7v2DateTime_WhenAddHyphensDate_ExceptionShouldBeThrown(string input)
public void GivenAnInvalidDateTime_WhenAddHyphensDate_ExceptionShouldBeThrown(string input)
{
Assert.Throws<RenderException>(() => Filters.AddHyphensDate(input));
var exception = Assert.Throws<RenderException>(() => Filters.AddHyphensDate(input));
Assert.Equal(FhirConverterErrorCode.InvalidDateTimeFormat, exception.FhirConverterErrorCode);
}

[Theory]
[MemberData(nameof(GetInvalidDataForFormatAsDateTime))]
public void GivenAnInvalidHl7v2DateTime_WhenFormatAsDateTime_ExceptionShouldBeThrown(string input)
public void GivenAnInvalidDateTime_WhenFormatAsDateTime_ExceptionShouldBeThrown(string input)
{
var exception = Assert.Throws<RenderException>(() => Filters.FormatAsDateTime(input));
Assert.Equal(FhirConverterErrorCode.InvalidDateTimeFormat, exception.FhirConverterErrorCode);
}

[Theory]
[MemberData(nameof(GetInvalidTimeZoneHandling))]
public void GivenAnInvalidTimeZoneHandling_WhenFormatAsDateTime_ExceptionShouldBeThrown(string input, string timeZoneHandling)
{
Assert.Throws<RenderException>(() => Filters.FormatAsDateTime(input));
var exception = Assert.Throws<RenderException>(() => Filters.FormatAsDateTime(input, timeZoneHandling));
Assert.Equal(FhirConverterErrorCode.InvalidTimeZoneHandling, exception.FhirConverterErrorCode);
}

[Fact]
Expand Down
41 changes: 23 additions & 18 deletions src/Microsoft.Health.Fhir.Liquid.Converter/Filters/DateFilters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public static string AddHyphensDate(string input)
return ConvertDate(input, groups);
}

public static string FormatAsDateTime(string input)
public static string FormatAsDateTime(string input, string timeZoneHandling = "local")
{
if (string.IsNullOrEmpty(input))
{
Expand All @@ -48,28 +48,33 @@ public static string FormatAsDateTime(string input)
}

var groups = matches[0].Groups;
if (groups["time"].Success)
if (!groups["time"].Success)
{
// Convert DateTime with time zone
string result = ConvertDateTime(input, groups);
if (groups["timeZone"].Success)
{
int timeZoneHour = int.Parse(groups["timeZoneHour"].Value);
int timeZoneMinute = int.Parse(groups["timeZoneMinute"].Value);
result += groups["sign"].Value + new TimeSpan(timeZoneHour, timeZoneMinute, 0).ToString(@"hh\:mm");
}
else
{
result += "Z";
}
return ConvertDate(input, groups);
}

return result;
// Convert DateTime with time zone
var result = ConvertDateTime(input, groups);
if (groups["timeZone"].Success)
{
var timeZoneHour = int.Parse(groups["timeZoneHour"].Value);
var timeZoneMinute = int.Parse(groups["timeZoneMinute"].Value);
result += groups["sign"].Value + new TimeSpan(timeZoneHour, timeZoneMinute, 0).ToString(@"hh\:mm");
}
else

if (!DateTime.TryParse(result, out var parsedResult))
{
// Convert Date
return ConvertDate(input, groups);
throw new RenderException(FhirConverterErrorCode.InvalidDateTimeFormat, string.Format(Resources.InvalidDateTimeFormat, input));
}

var dateTimeFormat = parsedResult.Millisecond == 0 ? "yyyy-MM-ddTHH:mm:ss%K" : "yyyy-MM-ddTHH:mm:ss.fff%K";
return timeZoneHandling?.ToLower() switch
{
"preserve" => result,
"utc" => TimeZoneInfo.ConvertTimeToUtc(parsedResult).ToString(dateTimeFormat),
"local" => parsedResult.ToString(dateTimeFormat),
_ => throw new RenderException(FhirConverterErrorCode.InvalidTimeZoneHandling, Resources.InvalidTimeZoneHandling),
};
}

public static string Now(string input, string format = "yyyy-MM-ddTHH:mm:ss.FFFZ")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public enum FhirConverterErrorCode
TimeoutError = 1306,
InvalidDateTimeFormat = 1307,
InvalidIdGenerationInput = 1308,
InvalidTimeZoneHandling = 1309,

// PostprocessException
JsonParsingError = 1401,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public static JObject ParseJson(string input)
ParseTreeWalker.Default.Walk(listener, tree);
var result = listener.GetResult().ToString();

return JObject.Parse(result);
return JsonConvert.DeserializeObject<JObject>(result, new JsonSerializerSettings { DateParseHandling = DateParseHandling.None });
}

public static JObject MergeJson(JObject obj)
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/Microsoft.Health.Fhir.Liquid.Converter/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@
<data name="InvalidIdGenerationInput" xml:space="preserve">
<value>The input to generate ID is invalid.</value>
</data>
<data name="InvalidTimeZoneHandling" xml:space="preserve">
<value>TimeZoneHandling only supports 'local', 'utc' and 'preserve'.</value>
</data>
<data name="JsonMergingError" xml:space="preserve">
<value>Error happened when merging JSON: {0}</value>
</data>
Expand Down

0 comments on commit 3ff7e83

Please sign in to comment.