diff --git a/src/Benchmarks/Benchmarks.csproj b/src/Benchmarks/Benchmarks.csproj
index b42668099c..2896720b8b 100644
--- a/src/Benchmarks/Benchmarks.csproj
+++ b/src/Benchmarks/Benchmarks.csproj
@@ -4,7 +4,6 @@
Exe
net6.0
Benchmarks
- Firely.Sdk.Benchmarks
Firely.Sdk.Benchmarks
diff --git a/src/Benchmarks/DateTimeBenchmark.cs b/src/Benchmarks/DateTimeBenchmark.cs
new file mode 100644
index 0000000000..7eb86dff16
--- /dev/null
+++ b/src/Benchmarks/DateTimeBenchmark.cs
@@ -0,0 +1,62 @@
+using BenchmarkDotNet.Attributes;
+using Hl7.Fhir.Model;
+using System;
+
+namespace Firely.Sdk.Benchmarks
+{
+ public class DateTimeBenchmarks
+ {
+ [GlobalSetup]
+ public void BenchmarkSetup()
+ {
+ _dateTimeInstance = new FhirDateTime(DATETIME);
+ _ = _dateTimeInstance.TryToDateTime(out var _); // trigger initial compile of regex
+
+ _dateInstance = new Date(DATE);
+ _ = _dateInstance.TryToDate(out var _); // trigger initial compile of regex
+ }
+
+ private const string DATETIME = "2023-07-11T13:00:00";
+ private FhirDateTime _dateTimeInstance;
+
+ [Benchmark]
+ public DateTimeOffset DateTimeToDTO_Uncached()
+ {
+ // Clear the cache each invocation
+ _dateTimeInstance.Value = DATETIME;
+ _ = _dateTimeInstance.TryToDateTimeOffset(TimeSpan.Zero, out var result);
+ _dateTimeInstance.Value = null;
+
+ return result;
+ }
+
+ [Benchmark]
+ public DateTimeOffset DateTimeToDTO_Cached()
+ {
+ _ = _dateTimeInstance.TryToDateTimeOffset(TimeSpan.Zero, out var result);
+ return result;
+ }
+
+ private const string DATE = "2023-07-11";
+ private Date _dateInstance;
+
+
+ [Benchmark]
+ public DateTimeOffset DateToDTO_Uncached()
+ {
+ // Clear the cache each invocation
+ _dateInstance.Value = DATETIME;
+ _ = _dateInstance.TryToDateTimeOffset(out var result);
+ _dateInstance.Value = null;
+
+ return result;
+ }
+
+ [Benchmark]
+ public DateTimeOffset DateToDTO_Cached()
+ {
+ _ = _dateInstance.TryToDateTimeOffset(out var result);
+ return result;
+ }
+ }
+}
diff --git a/src/Hl7.Fhir.Base/ElementModel/Types/Date.cs b/src/Hl7.Fhir.Base/ElementModel/Types/Date.cs
index 9012c5d11a..f1119f5202 100644
--- a/src/Hl7.Fhir.Base/ElementModel/Types/Date.cs
+++ b/src/Hl7.Fhir.Base/ElementModel/Types/Date.cs
@@ -17,10 +17,11 @@ namespace Hl7.Fhir.ElementModel.Types
{
public class Date : Any, IComparable, ICqlEquatable, ICqlOrderable, ICqlConvertible
{
- private Date(string original, DateTimeOffset parsedValue, DateTimePrecision precision, bool hasOffset)
+ private Date(DateTimeOffset value, DateTimePrecision precision, bool hasOffset)
{
- _original = original;
- _parsedValue = parsedValue;
+ if (precision > DateTimePrecision.Day) throw new ArgumentException($"Invalid precision {precision}, cannot be more than {nameof(DateTimePrecision.Day)}.", nameof(precision));
+
+ _value = DateTime.RoundToPrecision(value, precision, hasOffset);
Precision = precision;
HasOffset = hasOffset;
}
@@ -31,22 +32,10 @@ public static Date Parse(string representation) =>
public static bool TryParse(string representation, out Date value) => tryParse(representation, out value);
public static Date FromDateTimeOffset(DateTimeOffset dto, DateTimePrecision prec = DateTimePrecision.Day,
- bool includeOffset = false)
- {
- string formatString = prec switch
- {
- DateTimePrecision.Year => "yyyy",
- DateTimePrecision.Month => "yyyy-MM",
- _ => "yyyy-MM-dd",
- };
- if (includeOffset) formatString += "K";
-
- var representation = dto.ToString(formatString);
- return Parse(representation);
- }
+ bool includeOffset = false) => new(dto, prec, includeOffset);
- public DateTime ToDateTime() => new(_parsedValue, Precision, HasOffset);
+ public DateTime ToDateTime() => DateTime.FromDateTimeOffset(_value, Precision, HasOffset);
public static Date Today(bool includeOffset = false) => FromDateTimeOffset(DateTimeOffset.Now, includeOffset: includeOffset);
@@ -55,27 +44,33 @@ public static Date FromDateTimeOffset(DateTimeOffset dto, DateTimePrecision prec
///
public DateTimePrecision Precision { get; private set; }
- public int? Years => Precision >= DateTimePrecision.Year ? _parsedValue.Year : (int?)null;
- public int? Months => Precision >= DateTimePrecision.Month ? _parsedValue.Month : (int?)null;
- public int? Days => Precision >= DateTimePrecision.Day ? _parsedValue.Day : (int?)null;
+ public int? Years => Precision >= DateTimePrecision.Year ? _value.Year : (int?)null;
+ public int? Months => Precision >= DateTimePrecision.Month ? _value.Month : (int?)null;
+ public int? Days => Precision >= DateTimePrecision.Day ? _value.Day : (int?)null;
///
/// The span of time ahead/behind UTC
///
- public TimeSpan? Offset => HasOffset ? _parsedValue.Offset : (TimeSpan?)null;
-
- private readonly string _original;
- private readonly DateTimeOffset _parsedValue;
+ public TimeSpan? Offset => HasOffset ? _value.Offset : null;
///
/// Whether the time specifies an offset to UTC
///
public bool HasOffset { get; private set; }
- private static readonly string DATEFORMAT =
- $"(?[0-9]{{4}}) ((?-[0-9][0-9]) ((?-[0-9][0-9]) )?)? {Time.OFFSETFORMAT}?";
- public static readonly Regex PARTIALDATEREGEX = new("^" + DATEFORMAT + "$",
- RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled | RegexOptions.ExplicitCapture);
+ ///
+ /// If this instance was constructed using Parse(), this is the original
+ /// raw input to the parse. Used to guarantee roundtrippability.
+ ///
+ private string? _originalParsedString { get; init; }
+
+ private readonly DateTimeOffset _value;
+
+ ///
+ /// Converts the date to a full DateTimeOffset instance.
+ ///
+ public DateTimeOffset ToDateTimeOffset(TimeSpan defaultOffset) =>
+ HasOffset ? _value : new(_value.Ticks, defaultOffset);
///
/// Converts the date to a full DateTimeOffset instance.
@@ -98,8 +93,13 @@ public DateTimeOffset ToDateTimeOffset(int hours, int minutes, int seconds, Time
/// Offset used when the datetime does not specify one.
///
public DateTimeOffset ToDateTimeOffset(int hours, int minutes, int seconds, int milliseconds, TimeSpan defaultOffset) =>
- new(_parsedValue.Year, _parsedValue.Month, _parsedValue.Day, hours, minutes, seconds, milliseconds,
- HasOffset ? _parsedValue.Offset : defaultOffset);
+ new(_value.Year, _value.Month, _value.Day, hours, minutes, seconds, milliseconds,
+ HasOffset ? _value.Offset : defaultOffset);
+
+ private static readonly string DATEFORMAT =
+ $"(?[0-9]{{4}}) ((?-[0-9][0-9]) ((?-[0-9][0-9]) )?)? {Time.OFFSETFORMAT}?";
+ public static readonly Regex PARTIALDATEREGEX = new("^" + DATEFORMAT + "$",
+ RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled | RegexOptions.ExplicitCapture);
///
/// Converts the date to a full DateTimeOffset instance.
@@ -112,7 +112,7 @@ private static bool tryParse(string representation, out Date value)
var matches = PARTIALDATEREGEX.Match(representation);
if (!matches.Success)
{
- value = new Date(representation, default, default, default);
+ value = new Date(default, default, default);
return false;
}
@@ -133,7 +133,11 @@ private static bool tryParse(string representation, out Date value)
(offset.Success ? offset.Value : "Z");
var success = DateTimeOffset.TryParse(parseableDT, out var parsedValue);
- value = new Date(representation, parsedValue, prec, offset.Success);
+ value = new Date(parsedValue, prec, offset.Success)
+ {
+ _originalParsedString = representation
+ };
+
return success;
}
@@ -149,21 +153,21 @@ private static bool tryParse(string representation, out Date value)
var dto = addValue.Unit switch
{
// we can ignore precision, as the precision will "trim" it anyway, and if we add 13 months, then the year can tick over nicely
- "years" or "year" => dateValue._parsedValue.AddYears((int)addValue.Value),
+ "years" or "year" => dateValue._value.AddYears((int)addValue.Value),
"month" or "months" => dateValue.Precision == DateTimePrecision.Year
- ? dateValue._parsedValue.AddYears((int)(addValue.Value / 12))
- : dateValue._parsedValue.AddMonths((int)addValue.Value),
+ ? dateValue._value.AddYears((int)(addValue.Value / 12))
+ : dateValue._value.AddMonths((int)addValue.Value),
"week" or "weeks" or "wk" => dateValue.Precision switch
{
- DateTimePrecision.Year => dateValue._parsedValue.AddYears((int)(addValue.Value / 52)),
- DateTimePrecision.Month => dateValue._parsedValue.AddMonths((int)(addValue.Value * 7 / 30)),
- _ => dateValue._parsedValue.AddDays(((int)addValue.Value) * 7)
+ DateTimePrecision.Year => dateValue._value.AddYears((int)(addValue.Value / 52)),
+ DateTimePrecision.Month => dateValue._value.AddMonths((int)(addValue.Value * 7 / 30)),
+ _ => dateValue._value.AddDays(((int)addValue.Value) * 7)
},
"day" or "days" or "d" => dateValue.Precision switch
{
- DateTimePrecision.Year => dateValue._parsedValue.AddYears((int)(addValue.Value / 365)),
- DateTimePrecision.Month => dateValue._parsedValue.AddMonths((int)(addValue.Value / 30)),
- _ => dateValue._parsedValue.AddDays((int)addValue.Value)
+ DateTimePrecision.Year => dateValue._value.AddYears((int)(addValue.Value / 365)),
+ DateTimePrecision.Month => dateValue._value.AddMonths((int)(addValue.Value / 30)),
+ _ => dateValue._value.AddDays((int)addValue.Value)
},
_ => throw new ArgumentException($"'{addValue.Unit}' is not a valid time-valued unit", nameof(addValue)),
};
@@ -210,7 +214,7 @@ public Result TryCompareTo(Any other)
return other switch
{
null => 1,
- Date p => DateTime.CompareDateTimeParts(_parsedValue, Precision, HasOffset, p._parsedValue, p.Precision, p.HasOffset),
+ Date p => DateTime.CompareDateTimeParts(_value, Precision, HasOffset, p._value, p.Precision, p.HasOffset),
_ => throw NotSameTypeComparison(this, other)
};
}
@@ -221,8 +225,8 @@ public Result TryCompareTo(Any other)
public static bool operator >=(Date a, Date b) => a.CompareTo(b) >= 0;
- public override int GetHashCode() => _original.GetHashCode();
- public override string ToString() => _original;
+ public override int GetHashCode() => _value.GetHashCode();
+ public override string ToString() => _originalParsedString is not null ? _originalParsedString : DateTime.ToStringWithPrecision(_value, Precision, HasOffset);
public static implicit operator DateTime(Date pd) => pd.ToDateTime();
public static explicit operator Date(DateTimeOffset dto) => FromDateTimeOffset(dto);
diff --git a/src/Hl7.Fhir.Base/ElementModel/Types/DateTime.cs b/src/Hl7.Fhir.Base/ElementModel/Types/DateTime.cs
index b3191dd465..df4f406c07 100644
--- a/src/Hl7.Fhir.Base/ElementModel/Types/DateTime.cs
+++ b/src/Hl7.Fhir.Base/ElementModel/Types/DateTime.cs
@@ -17,28 +17,44 @@ namespace Hl7.Fhir.ElementModel.Types
{
public class DateTime : Any, IComparable, ICqlEquatable, ICqlOrderable, ICqlConvertible
{
- internal DateTime(DateTimeOffset value, DateTimePrecision precision, bool hasOffset)
+ private DateTime(DateTimeOffset value, DateTimePrecision precision, bool includeOffset)
{
- _value = value;
+ _value = RoundToPrecision(value, precision, includeOffset);
Precision = precision;
- HasOffset = hasOffset;
+ HasOffset = includeOffset;
}
public static DateTime Parse(string representation) =>
- TryParse(representation, out var result) ? result : throw new FormatException($"String '{representation}' was not recognized as a valid datetime.");
+ TryParse(representation, out var result) ? result! : throw new FormatException($"String '{representation}' was not recognized as a valid datetime.");
public static bool TryParse(string representation, out DateTime value) => tryParse(representation, out value);
- public static string FormatDateTimeOffset(DateTimeOffset dto) => dto.ToString(FMT_FULL);
-
- public static DateTime FromDateTimeOffset(DateTimeOffset dto) => new(dto, DateTimePrecision.Fraction, hasOffset: true);
+ ///
+ /// Rounds the contents of a to the given precision, unused precision if filled out
+ /// as midnight, the first of january, GMT.
+ ///
+ /// The to round.
+ /// The precision to round down to.
+ /// Whether to use the timezone specified, or round it to .
+ internal static DateTimeOffset RoundToPrecision(DateTimeOffset source, DateTimePrecision precision, bool withOffset) => precision switch
+ {
+ DateTimePrecision.Year => new DateTimeOffset(source.Year, 1, 1, 0, 0, 0, withOffset ? source.Offset : TimeSpan.Zero),
+ DateTimePrecision.Month => new DateTimeOffset(source.Year, source.Month, 1, 0, 0, 0, withOffset ? source.Offset : TimeSpan.Zero),
+ DateTimePrecision.Day => new DateTimeOffset(source.Year, source.Month, source.Day, 0, 0, 0, withOffset ? source.Offset : TimeSpan.Zero),
+ DateTimePrecision.Hour => new DateTimeOffset(source.Year, source.Month, source.Day, source.Hour, 0, 0, withOffset ? source.Offset : TimeSpan.Zero),
+ DateTimePrecision.Minute => new DateTimeOffset(source.Year, source.Month, source.Day, source.Hour, source.Minute, 0, withOffset ? source.Offset : TimeSpan.Zero),
+ DateTimePrecision.Second => new DateTimeOffset(source.Year, source.Month, source.Day, source.Hour, source.Minute, source.Second, withOffset ? source.Offset : TimeSpan.Zero),
+ _ => new DateTimeOffset(source.Ticks, withOffset ? source.Offset : TimeSpan.Zero),
+ };
+
+ public static DateTime FromDateTimeOffset(DateTimeOffset dto, DateTimePrecision prec = DateTimePrecision.Fraction, bool includeOffset = true) =>
+ new(dto, prec, includeOffset);
public static DateTime Now() => FromDateTimeOffset(DateTimeOffset.Now);
- public static DateTime Today() => new(DateTimeOffset.Now, DateTimePrecision.Day, hasOffset: true);
+ public static DateTime Today(bool includeOffset = true) => new(DateTimeOffset.Now, DateTimePrecision.Day, includeOffset);
- public Date TruncateToDate() => Date.FromDateTimeOffset(
- ToDateTimeOffset(_value.Offset), Precision, includeOffset: HasOffset);
+ public Date TruncateToDate() => Date.FromDateTimeOffset(_value, Precision > DateTimePrecision.Day ? DateTimePrecision.Day : Precision, HasOffset);
public int? Years => Precision >= DateTimePrecision.Year ? _value.Year : null;
public int? Months => Precision >= DateTimePrecision.Month ? _value.Month : null;
@@ -48,80 +64,11 @@ public Date TruncateToDate() => Date.FromDateTimeOffset(
public int? Seconds => Precision >= DateTimePrecision.Second ? _value.Second : null;
public int? Millis => Precision >= DateTimePrecision.Fraction ? _value.Millisecond : null;
- public static DateTime operator +(DateTime dateTimeValue, Quantity addValue)
- {
- if (dateTimeValue is null) throw new ArgumentNullException(nameof(dateTimeValue));
- if (addValue is null) throw new ArgumentNullException(nameof(addValue));
-
- // Based on the discussion on equality/comparisons here:
- // https://chat.fhir.org/#narrow/stream/179266-fhirpath/topic/Date.2FTime.20comparison.20vs.20equality
- // We have also allowed addition to use the definitve UCUM units of 'wk', 'd', 'h', 'min' as if they are a calendar unit of
- // 'week'/'day'/'hour'/'minute' respectively.
- var dto = addValue.Unit switch
- {
- // we can ignore precision, as the precision will "trim" it anyway, and if we add 13 months, then the year can tick over nicely
- "years" or "year" => dateTimeValue._value.AddYears((int)addValue.Value),
- "month" or "months" => dateTimeValue.Precision == DateTimePrecision.Year
- ? dateTimeValue._value.AddYears((int)(addValue.Value / 12))
- : dateTimeValue._value.AddMonths((int)addValue.Value),
- "week" or "weeks" or "wk" => dateTimeValue.Precision switch
- {
- DateTimePrecision.Year => dateTimeValue._value.AddYears((int)(addValue.Value / 52)),
- DateTimePrecision.Month => dateTimeValue._value.AddMonths((int)(addValue.Value * 7 / 30)),
- _ => dateTimeValue._value.AddDays(((int)addValue.Value) * 7)
- },
- "day" or "days" or "d" => dateTimeValue.Precision switch
- {
- DateTimePrecision.Year => dateTimeValue._value.AddYears((int)(addValue.Value / 365)),
- DateTimePrecision.Month => dateTimeValue._value.AddMonths((int)(addValue.Value / 30)),
- _ => dateTimeValue._value.AddDays((int)addValue.Value)
- },
-
- // NOT ignoring precision on time based stuff if there is no time component
- // if no time component, don't modify result
- "hour" or "hours" or "h" => dateTimeValue.Precision > DateTimePrecision.Day
- ? dateTimeValue._value.AddHours((double)addValue.Value)
- : dateTimeValue._value,
- "minute" or "minutes" or "min" => dateTimeValue.Precision > DateTimePrecision.Day
- ? dateTimeValue._value.AddMinutes((double)addValue.Value)
- : dateTimeValue._value,
- "s" or "second" or "seconds" => dateTimeValue.Precision > DateTimePrecision.Day
- ? dateTimeValue._value.AddSeconds((double)addValue.Value)
- : dateTimeValue._value,
- "ms" or "millisecond" or "milliseconds" => dateTimeValue.Precision > DateTimePrecision.Day
- ? dateTimeValue._value.AddMilliseconds((double)addValue.Value)
- : dateTimeValue._value,
- _ => throw new ArgumentException($"'{addValue.Unit}' is not a valid time-valued unit", nameof(addValue)),
- };
-
- var resultRepresentation = dto.ToString(FMT_FULL);
- var originalRepresentation = dateTimeValue.ToString();
-
- if (resultRepresentation.Length > originalRepresentation.Length)
- {
- // need to trim appropriately.
- if (dateTimeValue.Precision <= DateTimePrecision.Minute)
- resultRepresentation = resultRepresentation.Substring(0, originalRepresentation.Length);
- else
- {
- if (!dateTimeValue.HasOffset)
- {
- // trim the offset from it
- resultRepresentation = dto.ToString("yyyy-MM-dd'T'HH:mm:ss.FFFFFFF");
- }
- }
- }
-
- return Parse(resultRepresentation);
- }
-
///
/// The span of time ahead/behind UTC
///
public TimeSpan? Offset => HasOffset ? _value.Offset : null;
- private readonly DateTimeOffset _value;
-
///
/// The precision of the date and time available.
///
@@ -132,11 +79,13 @@ public Date TruncateToDate() => Date.FromDateTimeOffset(
///
public bool HasOffset { get; private set; }
- private static readonly string DATETIMEFORMAT =
- $"(?[0-9]{{4}}) ((?-[0-9][0-9]) ((?-[0-9][0-9]) (T{Time.TIMEFORMAT})?)?)? {Time.OFFSETFORMAT}?";
- private static readonly Regex DATETIMEREGEX =
- new("^" + DATETIMEFORMAT + "$",
- RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled | RegexOptions.ExplicitCapture);
+ ///
+ /// If this instance was constructed using Parse(), this is the original
+ /// raw input to the parse. Used to guarantee roundtrippability.
+ ///
+ private string? _originalParsedString { get; init; }
+
+ private readonly DateTimeOffset _value;
///
/// Converts the datetime to a full DateTimeOffset instance.
@@ -144,16 +93,16 @@ public Date TruncateToDate() => Date.FromDateTimeOffset(
/// Offset used when the datetime does not specify one.
///
public DateTimeOffset ToDateTimeOffset(TimeSpan defaultOffset) =>
- HasOffset switch
- {
- true => _value,
- false => new(_value.Year, _value.Month, _value.Day,
- _value.Hour, _value.Minute, _value.Second, _value.Millisecond,
- defaultOffset)
- };
+ HasOffset ? _value : new(_value.Ticks, defaultOffset);
public const string FMT_FULL = "yyyy-MM-dd'T'HH:mm:ss.FFFFFFFK";
+ private static readonly string DATETIMEFORMAT =
+ $"(?[0-9]{{4}}) ((?-[0-9][0-9]) ((?-[0-9][0-9]) (T{Time.TIMEFORMAT})?)?)? {Time.OFFSETFORMAT}?";
+ private static readonly Regex DATETIMEREGEX =
+ new("^" + DATETIMEFORMAT + "$",
+ RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled | RegexOptions.ExplicitCapture);
+
private static bool tryParse(string representation, out DateTime value)
{
if (representation is null) throw new ArgumentNullException(nameof(representation));
@@ -195,17 +144,78 @@ private static bool tryParse(string representation, out DateTime value)
var success = DateTimeOffset.TryParse(parseableDT, out var parsedValue);
value = new DateTime(parsedValue, prec, offset.Success)
{
- originalParsedString = representation
+ _originalParsedString = representation
};
return success;
}
- ///
- /// If this instance was constructed using Parse(), this is the original
- /// raw input to the parse. Used to guarantee roundtrippability.
- ///
- private string? originalParsedString { get; init; }
+ public static DateTime operator +(DateTime dateTimeValue, Quantity addValue)
+ {
+ if (dateTimeValue is null) throw new ArgumentNullException(nameof(dateTimeValue));
+ if (addValue is null) throw new ArgumentNullException(nameof(addValue));
+
+ // Based on the discussion on equality/comparisons here:
+ // https://chat.fhir.org/#narrow/stream/179266-fhirpath/topic/Date.2FTime.20comparison.20vs.20equality
+ // We have also allowed addition to use the definitve UCUM units of 'wk', 'd', 'h', 'min' as if they are a calendar unit of
+ // 'week'/'day'/'hour'/'minute' respectively.
+ var dto = addValue.Unit switch
+ {
+ // we can ignore precision, as the precision will "trim" it anyway, and if we add 13 months, then the year can tick over nicely
+ "years" or "year" => dateTimeValue._value.AddYears((int)addValue.Value),
+ "month" or "months" => dateTimeValue.Precision == DateTimePrecision.Year
+ ? dateTimeValue._value.AddYears((int)(addValue.Value / 12))
+ : dateTimeValue._value.AddMonths((int)addValue.Value),
+ "week" or "weeks" or "wk" => dateTimeValue.Precision switch
+ {
+ DateTimePrecision.Year => dateTimeValue._value.AddYears((int)(addValue.Value / 52)),
+ DateTimePrecision.Month => dateTimeValue._value.AddMonths((int)(addValue.Value * 7 / 30)),
+ _ => dateTimeValue._value.AddDays(((int)addValue.Value) * 7)
+ },
+ "day" or "days" or "d" => dateTimeValue.Precision switch
+ {
+ DateTimePrecision.Year => dateTimeValue._value.AddYears((int)(addValue.Value / 365)),
+ DateTimePrecision.Month => dateTimeValue._value.AddMonths((int)(addValue.Value / 30)),
+ _ => dateTimeValue._value.AddDays((int)addValue.Value)
+ },
+
+ // NOT ignoring precision on time based stuff if there is no time component
+ // if no time component, don't modify result
+ "hour" or "hours" or "h" => dateTimeValue.Precision > DateTimePrecision.Day
+ ? dateTimeValue._value.AddHours((double)addValue.Value)
+ : dateTimeValue._value,
+ "minute" or "minutes" or "min" => dateTimeValue.Precision > DateTimePrecision.Day
+ ? dateTimeValue._value.AddMinutes((double)addValue.Value)
+ : dateTimeValue._value,
+ "s" or "second" or "seconds" => dateTimeValue.Precision > DateTimePrecision.Day
+ ? dateTimeValue._value.AddSeconds((double)addValue.Value)
+ : dateTimeValue._value,
+ "ms" or "millisecond" or "milliseconds" => dateTimeValue.Precision > DateTimePrecision.Day
+ ? dateTimeValue._value.AddMilliseconds((double)addValue.Value)
+ : dateTimeValue._value,
+ _ => throw new ArgumentException($"'{addValue.Unit}' is not a valid time-valued unit", nameof(addValue)),
+ };
+
+ var resultRepresentation = dto.ToString(FMT_FULL);
+ var originalRepresentation = dateTimeValue.ToString();
+
+ if (resultRepresentation.Length > originalRepresentation.Length)
+ {
+ // need to trim appropriately.
+ if (dateTimeValue.Precision <= DateTimePrecision.Minute)
+ resultRepresentation = resultRepresentation.Substring(0, originalRepresentation.Length);
+ else
+ {
+ if (!dateTimeValue.HasOffset)
+ {
+ // trim the offset from it
+ resultRepresentation = dto.ToString("yyyy-MM-dd'T'HH:mm:ss.FFFFFFF");
+ }
+ }
+ }
+
+ return Parse(resultRepresentation);
+ }
///
/// Compare two datetimes based on CQL equality rules
@@ -307,12 +317,12 @@ internal static Result CompareDateTimeParts(DateTimeOffset l, DateTimePreci
public override int GetHashCode() => _value.GetHashCode();
- public override string ToString()
- {
- if (originalParsedString is not null) return originalParsedString;
+ public override string ToString() => _originalParsedString is not null ? _originalParsedString : ToStringWithPrecision(_value, Precision, HasOffset);
+ internal static string ToStringWithPrecision(DateTimeOffset dto, DateTimePrecision prec, bool includeOffset)
+ {
// "yyyy-MM-dd'T'HH:mm:ss.FFFFFFFK";
- var length = Precision switch
+ var length = prec switch
{
DateTimePrecision.Year => 4,
DateTimePrecision.Month => 7,
@@ -325,8 +335,8 @@ public override string ToString()
};
var format = FMT_FULL.Substring(0, length);
- if (HasOffset) format += 'K';
- return _value.ToString(format);
+ if (includeOffset) format += 'K';
+ return dto.ToString(format);
}
public static explicit operator DateTime(DateTimeOffset dto) => FromDateTimeOffset(dto);
@@ -357,5 +367,6 @@ public override string ToString()
Result ICqlConvertible.TryConvertToCode() => CannotCastTo(this);
Result ICqlConvertible.TryConvertToConcept() => CannotCastTo(this);
+ public static string FormatDateTimeOffset(DateTimeOffset dto) => dto.ToString(FMT_FULL);
}
}
diff --git a/src/Hl7.Fhir.Base/FhirPath/ElementNavFhirExtensions.cs b/src/Hl7.Fhir.Base/FhirPath/ElementNavFhirExtensions.cs
index 818e432695..e66eb4fe7b 100644
--- a/src/Hl7.Fhir.Base/FhirPath/ElementNavFhirExtensions.cs
+++ b/src/Hl7.Fhir.Base/FhirPath/ElementNavFhirExtensions.cs
@@ -215,7 +215,7 @@ internal static P.Any BoundaryDateTime(P.DateTime dt, long? precision, int month
return
(dtPrecision <= P.DateTimePrecision.Day) ?
P.Date.FromDateTimeOffset(dto, dtPrecision, dt.HasOffset) :
- new P.DateTime(dto, dtPrecision, dt.HasOffset);
+ P.DateTime.FromDateTimeOffset(dto, dtPrecision, dt.HasOffset);
}
internal static P.Time BoundaryTime(P.Time time, long? precision, int minutes, int seconds, int milliseconds)
@@ -273,7 +273,7 @@ internal static P.Time BoundaryTime(P.Time time, long? precision, int minutes, i
internal static bool? MemberOf(ITypedElement input, string valueset, EvaluationContext ctx)
{
var service = (ctx is FhirEvaluationContext fctx ? fctx.TerminologyService : null)
- ?? throw new ArgumentNullException("The 'memberOf' function cannot be executed because the FhirEvaluationContext does not include a TerminologyService.");
+ ?? throw new ArgumentNullException(nameof(ctx), "The 'memberOf' function cannot be executed because the FhirEvaluationContext does not include a TerminologyService.");
ValidateCodeParameters? inParams = new ValidateCodeParameters()
.WithValueSet(valueset);
diff --git a/src/Hl7.Fhir.Base/Model/Date-comparators.cs b/src/Hl7.Fhir.Base/Model/Date-comparators.cs
index 9c9d980ea5..e221a27b68 100644
--- a/src/Hl7.Fhir.Base/Model/Date-comparators.cs
+++ b/src/Hl7.Fhir.Base/Model/Date-comparators.cs
@@ -28,9 +28,6 @@ POSSIBILITY OF SUCH DAMAGE.
*/
-using System;
-using P = Hl7.Fhir.ElementModel.Types;
-
namespace Hl7.Fhir.Model
{
@@ -44,7 +41,7 @@ public partial class Date
if (aValue == null) return bValue == null;
if (bValue == null) return false;
- return P.DateTime.Parse(a.Value) > P.DateTime.Parse(b.Value);
+ return a.ToDate() > b.ToDate();
}
public static bool operator >=(Date a, Date b)
@@ -55,7 +52,7 @@ public partial class Date
if (aValue == null) return bValue == null;
if (bValue == null) return false;
- return P.DateTime.Parse(a.Value) >= P.DateTime.Parse(b.Value);
+ return a.ToDate() >= b.ToDate();
}
public static bool operator <(Date a, Date b)
@@ -66,7 +63,7 @@ public partial class Date
if (aValue == null) return bValue == null;
if (bValue == null) return false;
- return P.DateTime.Parse(a.Value) < P.DateTime.Parse(b.Value);
+ return a.ToDate() < b.ToDate();
}
public static bool operator <=(Date a, Date b)
@@ -77,7 +74,7 @@ public partial class Date
if (aValue == null) return bValue == null;
if (bValue == null) return false;
- return P.DateTime.Parse(a.Value) <= P.DateTime.Parse(b.Value);
+ return a.ToDate() <= b.ToDate();
}
///
@@ -105,12 +102,9 @@ public override bool Equals(object obj)
if (Value == null) return otherValue == null;
if (otherValue == null) return false;
- if (this.Value == otherValue) return true; // Default reference/string comparison works in most cases
-
- var left = P.DateTime.Parse(Value);
- var right = P.DateTime.Parse(otherValue);
+ if (Value == otherValue) return true; // Default reference/string comparison works in most cases
- return left == right;
+ return ToDate() == other.ToDate();
}
else
return false;
diff --git a/src/Hl7.Fhir.Base/Model/Date.cs b/src/Hl7.Fhir.Base/Model/Date.cs
index 0febdea184..cbfede1e61 100644
--- a/src/Hl7.Fhir.Base/Model/Date.cs
+++ b/src/Hl7.Fhir.Base/Model/Date.cs
@@ -28,8 +28,8 @@ POSSIBILITY OF SUCH DAMAGE.
*/
-using Hl7.Fhir.Serialization;
using System;
+using P = Hl7.Fhir.ElementModel.Types;
#nullable enable
@@ -38,41 +38,113 @@ namespace Hl7.Fhir.Model
public partial class Date
{
public Date(int year, int month, int day)
- : this(string.Format(FhirDateTime.FMT_YEARMONTHDAY, year, month, day))
+ : this(string.Format(System.Globalization.CultureInfo.InvariantCulture, FhirDateTime.FMT_YEARMONTHDAY, year, month, day))
{
}
public Date(int year, int month)
- : this(string.Format(FhirDateTime.FMT_YEARMONTH, year, month))
+ : this(string.Format(System.Globalization.CultureInfo.InvariantCulture, FhirDateTime.FMT_YEARMONTH, year, month))
{
}
- public Date(int year) : this(string.Format(FhirDateTime.FMT_YEAR, year))
+ public Date(int year) : this(string.Format(System.Globalization.CultureInfo.InvariantCulture, FhirDateTime.FMT_YEAR, year))
{
}
+ public static Date FromDateTimeOffset(DateTimeOffset date) => new(date.Year, date.Month, date.Day);
+
///
- /// Gets the current date in the local timezone
+ /// Gets the current date in the local timezone.
///
- /// Gets the current date in the local timezone
- public static Date Today() => new(DateTimeOffset.Now.ToString("yyyy-MM-dd"));
+ public static Date Today() => FromDateTimeOffset(DateTimeOffset.Now);
///
- /// Gets the current date in the timezone UTC
+ /// Gets the current date in UTC.
///
- /// Gets the current date in the timezone UTC
- public static Date UtcToday() => new(DateTimeOffset.UtcNow.ToString("yyyy-MM-dd"));
+ public static Date UtcToday() => FromDateTimeOffset(DateTimeOffset.UtcNow);
+
+ [NonSerialized] // To prevent binary serialization from serializing this field
+ private P.Date? _parsedValue = null;
+
+ private static readonly P.Date INVALID_VALUE = P.Date.Today();
///
- /// Converts this instance of a (partial) date into a .NET .
+ /// Converts a Fhir Date to a .
///
- public DateTimeOffset? ToDateTimeOffset() =>
- Value == null ? null : PrimitiveTypeConverter.ConvertTo(Value);
+ /// true if the Fhir Date contains a valid date string, false otherwise.
+ public bool TryToDate(out P.Date? date)
+ {
+ if (_parsedValue is null)
+ {
+ if (Value is not null && !(P.Date.TryParse(Value, out _parsedValue) && !_parsedValue!.HasOffset))
+ _parsedValue = INVALID_VALUE;
+ }
+
+ if (hasInvalidParsedValue())
+ {
+ date = null;
+ return false;
+ }
+ else
+ {
+ date = _parsedValue;
+ return true;
+ }
+
+ bool hasInvalidParsedValue() => ReferenceEquals(_parsedValue, INVALID_VALUE);
+ }
+
+ ///
+ /// Converts a Fhir Date to a .
+ ///
+ /// The Date, or null if the is null.
+ /// Thrown when the Value does not contain a valid FHIR Date.
+ public P.Date? ToDate() => TryToDate(out var dt) ? dt : throw new FormatException($"String '{Value}' was not recognized as a valid date.");
+
+ protected override void OnObjectValueChanged()
+ {
+ _parsedValue = null;
+ base.OnObjectValueChanged();
+ }
+
+ ///
+ /// Converts this Fhir Fhir Date to a .
+ ///
+ /// A DateTimeOffset filled out to midnight, january 1 (UTC) in case of a partial date.
+ public DateTimeOffset? ToDateTimeOffset()
+ {
+ if (Value == null) return null; // Note: this behaviour is inconsistent with ToDateTimeOffset() in FhirDateTime
+
+ // ToDateTimeOffset() will convert partial date/times by filling out to midnight/january 1 UTC
+ if (!TryToDate(out var dt))
+ throw new FormatException($"String '{Value}' was not recognized as a valid datetime.");
+
+ // Since Value is not null and the parsed value is valid, dto will not be null
+ return dt!.ToDateTimeOffset(TimeSpan.Zero);
+ }
+
+ ///
+ /// Convert this Fhir Date to a .
+ ///
+ /// True if the value of the Fhir Date is not null and can be parsed as a DateTimeOffset, false otherwise.
+ public bool TryToDateTimeOffset(out DateTimeOffset dto)
+ {
+ if (Value is not null && TryToDate(out var dt))
+ {
+ dto = dt!.ToDateTimeOffset(TimeSpan.Zero);
+ return true;
+ }
+ else
+ {
+ dto = default;
+ return false;
+ }
+ }
///
/// Checks whether the given literal is correctly formatted.
///
- public static bool IsValidValue(string value) => ElementModel.Types.Date.TryParse(value, out var parsed) && !parsed.HasOffset;
+ public static bool IsValidValue(string value) => P.Date.TryParse(value, out var parsed) && !parsed!.HasOffset;
}
}
diff --git a/src/Hl7.Fhir.Base/Model/FhirDateTime-comparators.cs b/src/Hl7.Fhir.Base/Model/FhirDateTime-comparators.cs
index 417f22c8a2..4a86368875 100644
--- a/src/Hl7.Fhir.Base/Model/FhirDateTime-comparators.cs
+++ b/src/Hl7.Fhir.Base/Model/FhirDateTime-comparators.cs
@@ -28,8 +28,6 @@ POSSIBILITY OF SUCH DAMAGE.
*/
-using P = Hl7.Fhir.ElementModel.Types;
-
namespace Hl7.Fhir.Model
{
public partial class FhirDateTime
@@ -103,10 +101,7 @@ public override bool Equals(object obj)
if (Value == null) return otherValue == null;
if (otherValue == null) return false;
- if (this.Value == otherValue) return true; // Default reference/string comparison works in most cases
-
- var left = P.DateTime.Parse(Value);
- var right = P.DateTime.Parse(otherValue);
+ if (Value == otherValue) return true; // Default reference/string comparison works in most cases
return ToDateTime() == other.ToDateTime();
}
diff --git a/src/Hl7.Fhir.Base/Model/FhirDateTime.cs b/src/Hl7.Fhir.Base/Model/FhirDateTime.cs
index ed11b32a54..98bb231e8e 100644
--- a/src/Hl7.Fhir.Base/Model/FhirDateTime.cs
+++ b/src/Hl7.Fhir.Base/Model/FhirDateTime.cs
@@ -32,7 +32,6 @@ POSSIBILITY OF SUCH DAMAGE.
using Hl7.Fhir.Serialization;
using System;
-using System.Text.RegularExpressions;
using P = Hl7.Fhir.ElementModel.Types;
namespace Hl7.Fhir.Model
@@ -59,15 +58,6 @@ public partial class FhirDateTime
///
public const string FMT_YEARMONTHDAY = "{0:D4}-{1:D2}-{2:D2}";
- private static readonly string DATEFORMAT =
- $"(?[0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000) (?-(0[1-9]|1[0-2]) (?-(0[1-9]|[1-2][0-9]|3[0-1])";
- private static readonly string TIMEFORMAT =
- $"(T(?[01][0-9]|2[0-3]) (?:[0-5][0-9]) (?:[0-5][0-9]|60)(?\\.[0-9]+) ?(?Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00)))?)?)?";
-
- private static readonly Regex DATETIMEREGEX =
- new("^" + DATEFORMAT + TIMEFORMAT + "$",
- RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled | RegexOptions.ExplicitCapture);
-
public FhirDateTime(DateTimeOffset dt) : this(PrimitiveTypeConverter.ConvertTo(dt))
{
}
@@ -129,6 +119,7 @@ public bool TryToDateTime(out P.DateTime? dateTime)
///
/// Converts a FhirDateTime to a .
///
+ /// The DateTime, or null if the is null.
/// Thrown when the Value does not contain a valid FHIR DateTime.
public P.DateTime? ToDateTime() => TryToDateTime(out var dt) ? dt : throw new FormatException($"String '{Value}' was not recognized as a valid datetime.");
@@ -150,7 +141,7 @@ protected override void OnObjectValueChanged()
/// effect on this, this merely converts the given Fhir datetime to the desired timezone
public DateTimeOffset ToDateTimeOffset(TimeSpan zone)
{
- if (this.Value == null) throw new InvalidOperationException("FhirDateTime's value is null");
+ if (Value == null) throw new InvalidOperationException("FhirDateTime's value is null");
// ToDateTimeOffset() will convert partial date/times by filling out to midnight/january 1 UTC
// When there's no timezone, the UTC is assumed
diff --git a/src/Hl7.Fhir.Support.Poco.Tests/Model/DateTests.cs b/src/Hl7.Fhir.Support.Poco.Tests/Model/DateTests.cs
new file mode 100644
index 0000000000..5ed71246af
--- /dev/null
+++ b/src/Hl7.Fhir.Support.Poco.Tests/Model/DateTests.cs
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2014, Firely (info@fire.ly) and contributors
+ * See the file CONTRIBUTORS for details.
+ *
+ * This file is licensed under the BSD 3-Clause license
+ * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE
+ */
+
+using FluentAssertions;
+using Hl7.Fhir.Model;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System;
+
+namespace Hl7.Fhir.Tests.Model
+{
+ [TestClass]
+ public class DateTests
+ {
+ [TestMethod]
+ public void DateHandling()
+ {
+ var dt = new Date(2010, 1, 1);
+ Assert.AreEqual("2010-01-01", dt.Value);
+
+ var dt2 = new Date(1972, 11, 30);
+ dt2.Value.Should().Be("1972-11-30");
+
+ var dtNoDay = new Date(2014, 12);
+ dtNoDay.Value.Should().Be("2014-12");
+
+ var stamp = new DateTimeOffset(1972, 11, 30, 15, 10, 0, TimeSpan.Zero);
+ dt = Date.FromDateTimeOffset(stamp);
+ dt.Value.Should().Be("1972-11-30");
+ }
+
+ [TestMethod]
+ public void TryToDateTimeOffset()
+ {
+ var fdt = new Date(2021, 3, 18);
+ fdt.TryToDateTimeOffset(out var dto1).Should().BeTrue();
+ Assert.AreEqual("2021-03-18T00:00:00.0000000+00:00", dto1.ToString("o"));
+
+ fdt = new Date(2021, 32, 18);
+ fdt.TryToDateTimeOffset(out var _).Should().BeFalse();
+
+ fdt = new Date("2021-03-18+01:00");
+ fdt.TryToDateTimeOffset(out var _).Should().BeFalse();
+
+ fdt = new Date("2021-32-18");
+ fdt.TryToDateTimeOffset(out var _).Should().BeFalse();
+ }
+
+ [TestMethod]
+ public void TodayTests()
+ {
+ var todayLocal = Date.Today();
+ Assert.AreEqual(DateTimeOffset.Now.ToString("yyy-MM-dd"), todayLocal.Value);
+
+ var todayUtc = Date.UtcToday();
+ Assert.AreEqual(DateTimeOffset.UtcNow.ToString("yyy-MM-dd"), todayUtc.Value);
+ }
+
+ [TestMethod]
+ public void UpdatesCachedValue()
+ {
+ var dft = new Date(2023, 07, 11);
+ dft.TryToDateTimeOffset(out var dto).Should().BeTrue();
+ dft.TryToDateTimeOffset(out var dto2).Should().BeTrue();
+ dto.Equals(dto2).Should().BeTrue();
+
+ dft.Value = "2023-07-11";
+ dft.TryToDateTimeOffset(out dto).Should().BeTrue();
+ dto.Day.Should().Be(11);
+ dft.TryToDateTimeOffset(out dto2).Should().BeTrue();
+ dto.Equals(dto2).Should().BeTrue();
+
+ dft.ObjectValue = "2023-07-11";
+ dft.TryToDateTimeOffset(out dto).Should().BeTrue();
+ dto.Month.Should().Be(7);
+ dft.TryToDateTimeOffset(out dto2).Should().BeTrue();
+ dto.Equals(dto2).Should().BeTrue();
+
+ dft.Value = null;
+ dft.TryToDateTimeOffset(out _).Should().BeFalse();
+ dft.ToDateTimeOffset().Should().BeNull();
+ }
+
+ [TestMethod]
+ public void ToDateTimeOffsetThrowsInvalidFormat()
+ {
+ var dft = new Date("T45:45:56");
+
+ Assert.ThrowsException(() => dft.ToDateTimeOffset());
+
+ dft.TryToDateTimeOffset(out var _).Should().BeFalse();
+ }
+
+ [TestMethod]
+ public void CanConvertToDateTime()
+ {
+ var dft = new Date(2023, 07, 11);
+ dft.TryToDate(out var dt).Should().BeTrue();
+ dt.Days.Should().Be(11);
+ dt.Precision.Should().Be(ElementModel.Types.DateTimePrecision.Day);
+
+ dft = new Date(2023, 7);
+ dft.TryToDate(out dt).Should().BeTrue();
+ dt.Days.Should().BeNull();
+ dt.Precision.Should().Be(ElementModel.Types.DateTimePrecision.Month);
+
+ dft = new Date(null);
+ dft.TryToDate(out dt).Should().BeTrue();
+ dt.Should().BeNull();
+ }
+ }
+}
diff --git a/src/Hl7.Fhir.Support.Poco.Tests/Model/DateTimeTests.cs b/src/Hl7.Fhir.Support.Poco.Tests/Model/DateTimeTests.cs
index dbb10f8d95..87c041bdc8 100644
--- a/src/Hl7.Fhir.Support.Poco.Tests/Model/DateTimeTests.cs
+++ b/src/Hl7.Fhir.Support.Poco.Tests/Model/DateTimeTests.cs
@@ -10,7 +10,6 @@
using Hl7.Fhir.Model;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
-using System.Diagnostics;
namespace Hl7.Fhir.Tests.Model
{
@@ -20,7 +19,7 @@ public class DateTimeTests
[TestMethod]
public void DateTimeHandling()
{
- FhirDateTime dt = new FhirDateTime("2010-01-01");
+ FhirDateTime dt = new FhirDateTime(2010, 1, 1);
Assert.AreEqual("2010-01-01", dt.Value);
FhirDateTime dt2 = new FhirDateTime(1972, 11, 30, 15, 10, 0, TimeSpan.Zero);
@@ -172,38 +171,14 @@ public void CanConvertToDateTime()
}
[TestMethod]
- public void CacheImprovesSpeed()
+ public void RetainsFractions()
{
- var dts = "2023-07-11T13:00:00";
- var dt = new FhirDateTime(dts);
- _ = dt.TryToDateTime(out var _); // trigger initial compile of regex
+ var input = @"2020-04-17T10:24:13.1882432-05:00";
+ var datetime = ElementModel.Types.DateTime.Parse(input);
+ var offset = datetime.ToDateTimeOffset(TimeSpan.Zero);
+ var output = ElementModel.Types.DateTime.FormatDateTimeOffset(offset);
- var sw = Stopwatch.StartNew();
-
- for (var i = 0; i < 1000; i++)
- {
- // Clear the cache each invocation
- dt.Value = dts;
- _ = dt.TryToDateTimeOffset(TimeSpan.Zero, out var _);
- dt.Value = null;
- }
-
- sw.Stop();
- Console.WriteLine(sw.Elapsed.ToString());
-
- dt = new FhirDateTime(dts);
-
- var sw2 = Stopwatch.StartNew();
- for (var i = 0; i < 1000; i++)
- {
- _ = dt.TryToDateTimeOffset(TimeSpan.Zero, out var _);
- }
- sw2.Stop();
-
- Console.WriteLine(sw2.Elapsed.ToString());
-
- // It's actually about 20x faster on my machine
- (sw2.Elapsed).Should().BeLessThan(sw.Elapsed);
+ output.Should().Be(input);
}
}
}
diff --git a/src/Hl7.Fhir.Support.Tests/ElementModel/DateTest.cs b/src/Hl7.Fhir.Support.Tests/ElementModel/DateTest.cs
index 9421b20993..dce3798c45 100644
--- a/src/Hl7.Fhir.Support.Tests/ElementModel/DateTest.cs
+++ b/src/Hl7.Fhir.Support.Tests/ElementModel/DateTest.cs
@@ -1,4 +1,5 @@
-using Microsoft.VisualStudio.TestTools.UnitTesting;
+using FluentAssertions;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using P = Hl7.Fhir.ElementModel.Types;
@@ -27,7 +28,7 @@ public void DateConstructor()
reject("2010-2-04");
}
- void accept(string testInput, int? y, int? m, int? d, P.DateTimePrecision? p, TimeSpan? o)
+ private void accept(string testInput, int? y, int? m, int? d, P.DateTimePrecision? p, TimeSpan? o)
{
Assert.IsTrue(P.Date.TryParse(testInput, out P.Date parsed), "TryParse");
Assert.AreEqual(y, parsed.Years, "years");
@@ -38,7 +39,7 @@ void accept(string testInput, int? y, int? m, int? d, P.DateTimePrecision? p, Ti
Assert.AreEqual(testInput, parsed.ToString(), "ToString");
}
- void reject(string testValue)
+ private void reject(string testValue)
{
Assert.IsFalse(P.Date.TryParse(testValue, out _));
}
@@ -135,5 +136,27 @@ public void FromDateTimeOffset()
Assert.AreEqual(plusOne, partialDate.Offset);
}
+ [TestMethod]
+ [DataRow("2001")]
+ [DataRow("2001-04")]
+ [DataRow("2001-04-06")]
+ [DataRow("2001-04-06+01:30")]
+ public void CanConvertToOriginalString(string format)
+ {
+ var parsed = P.Date.Parse(format);
+ parsed.ToString().Should().Be(format);
+ }
+
+ [TestMethod]
+ [DataRow(P.DateTimePrecision.Year, false, "2001")]
+ [DataRow(P.DateTimePrecision.Month, false, "2001-04")]
+ [DataRow(P.DateTimePrecision.Day, false, "2001-04-06")]
+ [DataRow(P.DateTimePrecision.Day, true, "2001-04-06+01:00")]
+ public void CanConvertToString(P.DateTimePrecision p, bool hasOffset, string expected)
+ {
+ var dt = new DateTimeOffset(2001, 4, 6, 13, 1, 2, 890, TimeSpan.FromHours(1));
+ var parsed = P.Date.FromDateTimeOffset(dt, p, hasOffset);
+ parsed.ToString().Should().Be(expected);
+ }
}
}
\ No newline at end of file
diff --git a/src/Hl7.Fhir.Support.Tests/ElementModel/DateTimeTest.cs b/src/Hl7.Fhir.Support.Tests/ElementModel/DateTimeTest.cs
index 7bb60ff0dc..cc3cf5944e 100644
--- a/src/Hl7.Fhir.Support.Tests/ElementModel/DateTimeTest.cs
+++ b/src/Hl7.Fhir.Support.Tests/ElementModel/DateTimeTest.cs
@@ -172,8 +172,26 @@ public void CanConvertToOriginalString(string format)
public void CanConvertToString(P.DateTimePrecision p, bool hasOffset, string expected)
{
var dt = new DateTimeOffset(2001, 4, 6, 13, 1, 2, 890, TimeSpan.FromHours(1));
- var parsed = new P.DateTime(dt, p, hasOffset);
+ var parsed = P.DateTime.FromDateTimeOffset(dt, p, hasOffset);
parsed.ToString().Should().Be(expected);
}
+
+
+ [TestMethod]
+ [DataRow(P.DateTimePrecision.Year, false, "2001-01-01T00:00:00+00:00")]
+ [DataRow(P.DateTimePrecision.Month, false, "2001-04-01T00:00:00+00:00")]
+ [DataRow(P.DateTimePrecision.Day, false, "2001-04-06T00:00:00+00:00")]
+ [DataRow(P.DateTimePrecision.Day, true, "2001-04-06T00:00:00+01:00")]
+ [DataRow(P.DateTimePrecision.Hour, false, "2001-04-06T13:00:00+00:00")]
+ [DataRow(P.DateTimePrecision.Minute, false, "2001-04-06T13:01:00+00:00")]
+ [DataRow(P.DateTimePrecision.Second, false, "2001-04-06T13:01:02+00:00")]
+ [DataRow(P.DateTimePrecision.Second, true, "2001-04-06T13:01:02+01:00")]
+ [DataRow(P.DateTimePrecision.Fraction, true, "2001-04-06T13:01:02.89+01:00")]
+ public void CanRound(P.DateTimePrecision p, bool hasOffset, string expected)
+ {
+ var dt = new DateTimeOffset(2001, 4, 6, 13, 1, 2, 890, TimeSpan.FromHours(1));
+ var rounded = P.DateTime.RoundToPrecision(dt, p, hasOffset);
+ P.DateTime.FormatDateTimeOffset(rounded).Should().Be(expected);
+ }
}
}
\ No newline at end of file
diff --git a/src/Hl7.FhirPath.Tests/Tests/BasicFunctionTests.cs b/src/Hl7.FhirPath.Tests/Tests/BasicFunctionTests.cs
index b60120706b..00d6831050 100644
--- a/src/Hl7.FhirPath.Tests/Tests/BasicFunctionTests.cs
+++ b/src/Hl7.FhirPath.Tests/Tests/BasicFunctionTests.cs
@@ -10,9 +10,8 @@
//extern alias dstu2;
using Hl7.Fhir.ElementModel;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-using System.Collections.Generic;
using Hl7.FhirPath.Functions;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Linq;
using P = Hl7.Fhir.ElementModel.Types;