Skip to content

Commit 0d74d39

Browse files
committed
Refactor UCUM unit handling and improve maintainability
Replaced hardcoded unit strings with `UCUMUnits` constants across the codebase to improve readability, maintainability, and consistency. Added new constants for various UCUM units, including `Year`, `Month`, `Day`, and others. Introduced `DaysPerYearDouble` and `DaysPerMonthDouble` for better precision in date calculations. Updated `CqlDateTime.cs`, `CqlDateTimeMath.cs`, and `CqlTime.cs` to use these constants, consolidating logic and improving error handling. Added `NotImplementedException` placeholders for certain cases requiring further implementation. Removed the unused `NormalizeTo` method. Enhanced inline documentation and standardized unit handling to reduce redundancy and potential errors.
1 parent b282ba0 commit 0d74d39

File tree

4 files changed

+143
-103
lines changed

4 files changed

+143
-103
lines changed

Cql/Cql.Abstractions/Abstractions/UCUMUnits.cs

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
* available at https://raw.githubusercontent.com/FirelyTeam/firely-cql-sdk/main/LICENSE
77
*/
88

9-
using Hl7.Cql.Iso8601;
10-
119
namespace Hl7.Cql.Abstractions
1210
{
1311
/// <summary>
@@ -28,66 +26,90 @@ public static class UCUMUnits
2826
public const string Unary = "1";
2927

3028
/// <summary>
31-
/// Years (annos in Latin).
29+
/// Years ("Annos" in Latin).
3230
/// </summary>
31+
/// <remarks>
32+
/// Note that this unit is the same as the value of <see cref="DaysPerYearDouble"/> which is 365.25 days.
33+
/// </remarks>
3334
public const string Year = "a";
35+
36+
/// <summary>
37+
/// Defines days per year
38+
/// </summary>
39+
/// <remarks>
40+
/// Used when specifying <see cref="Year"/>.
41+
/// </remarks>
42+
public const double DaysPerYearDouble = 365.25d;
43+
3444
/// <summary>
3545
/// Months
3646
/// </summary>
47+
/// <remarks>
48+
/// Note that this unit is the same as the value of <see cref="DaysPerMonthDouble"/> which is 30.4375 days.
49+
/// </remarks>
3750
public const string Month = "mo";
51+
52+
/// <summary>
53+
/// Defines days per month
54+
/// </summary>
55+
/// <remarks>
56+
/// Used when specifying <see cref="Month"/>.
57+
/// </remarks>
58+
public const double DaysPerMonthDouble = 30.4375d;
59+
3860
/// <summary>
3961
/// Days
4062
/// </summary>
4163
public const string Day = "d";
64+
4265
/// <summary>
4366
/// Hours
4467
/// </summary>
4568
public const string Hour = "h";
69+
4670
/// <summary>
4771
/// Minutes
4872
/// </summary>
4973
public const string Minute = "min";
74+
5075
/// <summary>
5176
/// Seconds
5277
/// </summary>
5378
public const string Second = "s";
79+
5480
/// <summary>
5581
/// Milliseconds
5682
/// </summary>
5783
public const string Millisecond = "ms";
84+
5885
/// <summary>
5986
/// Weeks, equal to 7 <see cref="Day"/>.
6087
/// </summary>
6188
public const string Week = "wk";
89+
6290
/// <summary>
6391
/// Imperial inches
6492
/// </summary>
6593
public const string Inch = "[in_i]";
94+
6695
/// <summary>
6796
/// Imperial feet
6897
/// </summary>
6998
public const string Foot = "[ft_i]";
99+
70100
/// <summary>
71101
/// Imperial yards
72102
/// </summary>
73103
public const string Yard = "[yd_i]";
104+
74105
/// <summary>
75106
/// Meters
76107
/// </summary>
77108
public const string Meter = "m";
109+
78110
/// <summary>
79111
/// Centimeters
80112
/// </summary>
81113
public const string Centimeter = "cm";
82-
/// <summary>
83-
/// Defines days per year
84-
/// </summary>
85-
public const double DaysPerYearDouble = 365.25d;
86-
/// <summary>
87-
/// Defines days per month
88-
/// </summary>
89-
public const double DaysPerMonthDouble = 30.4375d;
90114
}
91-
92-
93115
}

Cql/Cql.Abstractions/Primitives/CqlDateTime.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ public static bool TryParse(string s, out CqlDateTime? cqlDateTime)
155155
var dto = Value.DateTimeOffset;
156156
dto = unit switch
157157
{
158-
"a" => dto.AddDays(Math.Sign(value) * UCUMUnits.DaysPerYearDouble),
158+
UCUMUnits.Year => dto.AddDays(Math.Sign(value) * UCUMUnits.DaysPerYearDouble),
159159
"year" or "years" => dto.AddYears((int)value),
160160
"mo" => dto.AddDays(Math.Sign(value) * UCUMUnits.DaysPerMonthDouble),
161161
"month" or "months" => dto.AddMonths((int)value),

Cql/Cql.Abstractions/Primitives/CqlDateTimeMath.cs

Lines changed: 100 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,26 @@ internal static class CqlDateTimeMath
2828
/// <seealso href="https://cql.hl7.org/09-b-cqlreference.html#difference"/>
2929
internal static int? BoundariesBetween(DateTimeOffset? low, DateTimeOffset? high, string? precision)
3030
{
31-
if (low == null || high == null || precision == null)
31+
if (low is not {} firstDto || high is not {} secondDto || precision is null)
3232
return null;
3333

34-
var firstDto = low.Value;
35-
var secondDto = high.Value;
3634
switch (precision)
3735
{
36+
case UCUMUnits.Year:
37+
throw new NotImplementedException();
38+
3839
case "year":
3940
var yearDiff = (secondDto.Year - firstDto.Year);
4041
return yearDiff;
42+
43+
case UCUMUnits.Month:
44+
throw new NotImplementedException();
45+
4146
case "month":
4247
var monthDiff = (12 * (secondDto.Year - firstDto.Year) + secondDto.Month - firstDto.Month);
4348
return monthDiff;
44-
case "week":
49+
50+
case "week" or UCUMUnits.Week:
4551
{
4652
var span = secondDto.Subtract(firstDto);
4753
var weeks = span.TotalDays / 7d;
@@ -52,7 +58,8 @@ internal static class CqlDateTimeMath
5258
return asInt + 1;
5359
else return asInt;
5460
}
55-
case "day":
61+
62+
case "day" or UCUMUnits.Day:
5663
{
5764
var span = secondDto.Subtract(firstDto);
5865
var asInt = (int)span.TotalDays;
@@ -64,7 +71,8 @@ internal static class CqlDateTimeMath
6471
}
6572
else return asInt;
6673
}
67-
case "hour":
74+
75+
case "hour" or UCUMUnits.Hour:
6876
{
6977
var span = secondDto.Subtract(firstDto);
7078
var asInt = (int)span.TotalHours;
@@ -76,7 +84,8 @@ internal static class CqlDateTimeMath
7684
}
7785
else return asInt;
7886
}
79-
case "minute":
87+
88+
case "minute" or UCUMUnits.Minute:
8089
{
8190
var span = secondDto.Subtract(firstDto);
8291
var asInt = (int)span.TotalMinutes;
@@ -88,7 +97,8 @@ internal static class CqlDateTimeMath
8897
}
8998
else return asInt;
9099
}
91-
case "second":
100+
101+
case "second" or UCUMUnits.Second:
92102
{
93103
var span = secondDto.Subtract(firstDto);
94104
var asInt = (int)span.TotalSeconds;
@@ -100,7 +110,8 @@ internal static class CqlDateTimeMath
100110
}
101111
else return asInt;
102112
}
103-
case "millisecond":
113+
114+
case "millisecond" or UCUMUnits.Millisecond:
104115
{
105116
var span = secondDto.Subtract(firstDto);
106117
var asInt = (int)span.TotalMilliseconds;
@@ -112,20 +123,22 @@ internal static class CqlDateTimeMath
112123
}
113124
else return asInt;
114125
}
126+
115127
default: throw new ArgumentException($"Unit '{precision}' is not supported.");
116128
}
117129
}
118130

119-
internal static int? WholeCalendarPeriodsBetween(DateTimeOffset? low, DateTimeOffset? high, string precision)
131+
internal static int? WholeCalendarPeriodsBetween(DateTimeOffset? low, DateTimeOffset? high, string? precision)
120132
{
121-
if (low == null || high == null || precision == null)
133+
if (low is not {} firstDto || high is not {} secondDto || precision == null)
122134
return null;
123135

124136
var calendar = new GregorianCalendar();
125-
var firstDto = low.Value;
126-
var secondDto = high.Value;
127137
switch (precision)
128138
{
139+
case UCUMUnits.Year:
140+
throw new NotImplementedException();
141+
129142
case "year":
130143
var yearDiff = secondDto.Year - firstDto.Year;
131144
var firstDayInYear = firstDto.DayOfYear;
@@ -192,20 +205,25 @@ internal static class CqlDateTimeMath
192205
else if (yearDiff < 0 && firstDayInYear < secondDayInYear)
193206
yearDiff += 1;
194207
return yearDiff;
208+
209+
case UCUMUnits.Month:
210+
throw new NotImplementedException();
211+
195212
case "month":
196213
var monthDiff = (12 * (secondDto.Year - firstDto.Year) + secondDto.Month - firstDto.Month);
197214
if (monthDiff > 0 && secondDto.Day < firstDto.Day)
198215
monthDiff -= 1;
199216
else if (monthDiff < 0 && firstDto.Day < secondDto.Day)
200217
monthDiff += 1;
201218
return monthDiff;
202-
case "week": return (int)(secondDto.Subtract(firstDto).TotalDays / DaysPerWeekDouble);
203-
case "day": return (int)secondDto.Subtract(firstDto).TotalDays;
204-
case "hour": return (int)secondDto.Subtract(firstDto).TotalHours;
205-
case "minute": return (int)secondDto.Subtract(firstDto).TotalMinutes;
206-
case "second": return (int)secondDto.Subtract(firstDto).TotalSeconds;
207-
case "millisecond": return (int)secondDto.Subtract(firstDto).TotalMilliseconds;
208-
default: throw new ArgumentException($"Unit '{precision}' is not supported.");
219+
220+
case "week" or UCUMUnits.Week: return (int)(secondDto.Subtract(firstDto).TotalDays / DaysPerWeekDouble);
221+
case "day" or UCUMUnits.Day: return (int)secondDto.Subtract(firstDto).TotalDays;
222+
case "hour" or UCUMUnits.Hour: return (int)secondDto.Subtract(firstDto).TotalHours;
223+
case "minute" or UCUMUnits.Minute: return (int)secondDto.Subtract(firstDto).TotalMinutes;
224+
case "second" or UCUMUnits.Second: return (int)secondDto.Subtract(firstDto).TotalSeconds;
225+
case "millisecond" or UCUMUnits.Millisecond: return (int)secondDto.Subtract(firstDto).TotalMilliseconds;
226+
default: throw new ArgumentException($"Unit '{precision}' is not supported.");
209227
}
210228
}
211229

@@ -220,67 +238,67 @@ internal static class CqlDateTimeMath
220238
{ DateTimePrecision.Year, new CqlQuantity(1m, "year") },
221239
};
222240

223-
/// <summary>
224-
/// For datetime addition and subtraction, when quantity is more precise than the datetime,
225-
/// the quantity has to be normalized to the lesser precision and truncated.
226-
/// </summary>
227-
/// <see href="https://cql.hl7.org/09-b-cqlreference.html#add-1" />
228-
internal static CqlQuantity NormalizeTo(this CqlQuantity quantity, DateTimePrecision target)
229-
{
230-
// using the table found here:
231-
// https://cql.hl7.org/09-b-cqlreference.html#equivalent
232-
return (quantity.unit, target) switch
233-
{
234-
(null, _) => quantity,
235-
("mo", DateTimePrecision.Year) =>
236-
new CqlQuantity(Math.Truncate((quantity.value ?? 0) / 12)!, UCUMUnits.Year),
237-
238-
("d", DateTimePrecision.Year) =>
239-
new CqlQuantity(Math.Truncate((quantity.value ?? 0) / 365)!, UCUMUnits.Year),
240-
("d", DateTimePrecision.Month) =>
241-
new CqlQuantity(Math.Truncate((quantity.value ?? 0) / 30)!, UCUMUnits.Month),
242-
243-
("h", DateTimePrecision.Year) =>
244-
new CqlQuantity(Math.Truncate(((quantity.value ?? 0) / 24) / 365)!, UCUMUnits.Year),
245-
("h", DateTimePrecision.Month) =>
246-
new CqlQuantity(Math.Truncate(((quantity.value ?? 0) / 24) / 30)!, UCUMUnits.Month),
247-
("h", DateTimePrecision.Day) =>
248-
new CqlQuantity(Math.Truncate((quantity.value ?? 0) / 24)!, UCUMUnits.Day),
249-
250-
("mi", DateTimePrecision.Year) =>
251-
new CqlQuantity(Math.Truncate((((quantity.value ?? 0) / 60) / 24) / 365)!, UCUMUnits.Year),
252-
("mi", DateTimePrecision.Month) =>
253-
new CqlQuantity(Math.Truncate((((quantity.value ?? 0) / 60) / 24) / 30)!, UCUMUnits.Month),
254-
("mi", DateTimePrecision.Day) =>
255-
new CqlQuantity(Math.Truncate(((quantity.value ?? 0) / 60) / 24)!, UCUMUnits.Day),
256-
("mi", DateTimePrecision.Hour) =>
257-
new CqlQuantity(Math.Truncate((quantity.value ?? 0) / 60)!, UCUMUnits.Hour),
258-
259-
("s", DateTimePrecision.Year) =>
260-
new CqlQuantity(Math.Truncate(((((quantity.value ?? 0) / 60) / 60) / 24) / 365)!, UCUMUnits.Year),
261-
("s", DateTimePrecision.Month) =>
262-
new CqlQuantity(Math.Truncate(((((quantity.value ?? 0) / 60) / 60) / 24) / 30)!, UCUMUnits.Month),
263-
("s", DateTimePrecision.Day) =>
264-
new CqlQuantity(Math.Truncate((((quantity.value ?? 0) / 60) / 60) / 24)!, UCUMUnits.Day),
265-
("s", DateTimePrecision.Hour) =>
266-
new CqlQuantity(Math.Truncate(((quantity.value ?? 0) / 60) / 60)!, UCUMUnits.Hour),
267-
("s", DateTimePrecision.Minute) =>
268-
new CqlQuantity(Math.Truncate((quantity.value ?? 0) / 60)!, UCUMUnits.Minute),
269-
270-
("ms", DateTimePrecision.Year) =>
271-
new CqlQuantity(Math.Truncate((((((quantity.value ?? 0) / 1000) / 60) / 60) / 24) / 365)!, UCUMUnits.Year),
272-
("ms", DateTimePrecision.Month) =>
273-
new CqlQuantity(Math.Truncate((((((quantity.value ?? 0) / 1000) / 60) / 60) / 24) / 30)!, UCUMUnits.Month),
274-
("ms", DateTimePrecision.Day) =>
275-
new CqlQuantity(Math.Truncate(((((quantity.value ?? 0) / 1000) / 60) / 60) / 24)!, UCUMUnits.Day),
276-
("ms", DateTimePrecision.Hour) =>
277-
new CqlQuantity(Math.Truncate((((quantity.value ?? 0) / 1000) / 60) / 60)!, UCUMUnits.Hour),
278-
("ms", DateTimePrecision.Minute) =>
279-
new CqlQuantity(Math.Truncate(((quantity.value ?? 0) / 1000) / 60)!, UCUMUnits.Minute),
280-
("ms", DateTimePrecision.Second) =>
281-
new CqlQuantity(Math.Truncate((quantity.value ?? 0) / 1000)!, UCUMUnits.Second),
282-
(_,_) => quantity
283-
};
284-
}
241+
// /// <summary>
242+
// /// For datetime addition and subtraction, when quantity is more precise than the datetime,
243+
// /// the quantity has to be normalized to the lesser precision and truncated.
244+
// /// </summary>
245+
// /// <see href="https://cql.hl7.org/09-b-cqlreference.html#add-1" />
246+
// internal static CqlQuantity NormalizeTo(this CqlQuantity quantity, DateTimePrecision target)
247+
// {
248+
// // using the table found here:
249+
// // https://cql.hl7.org/09-b-cqlreference.html#equivalent
250+
// return (quantity.unit, target) switch
251+
// {
252+
// (null, _) => quantity,
253+
// ("mo", DateTimePrecision.Year) =>
254+
// new CqlQuantity(Math.Truncate((quantity.value ?? 0) / 12)!, UCUMUnits.Year),
255+
//
256+
// ("d", DateTimePrecision.Year) =>
257+
// new CqlQuantity(Math.Truncate((quantity.value ?? 0) / 365)!, UCUMUnits.Year),
258+
// ("d", DateTimePrecision.Month) =>
259+
// new CqlQuantity(Math.Truncate((quantity.value ?? 0) / 30)!, UCUMUnits.Month),
260+
//
261+
// ("h", DateTimePrecision.Year) =>
262+
// new CqlQuantity(Math.Truncate(((quantity.value ?? 0) / 24) / 365)!, UCUMUnits.Year),
263+
// ("h", DateTimePrecision.Month) =>
264+
// new CqlQuantity(Math.Truncate(((quantity.value ?? 0) / 24) / 30)!, UCUMUnits.Month),
265+
// ("h", DateTimePrecision.Day) =>
266+
// new CqlQuantity(Math.Truncate((quantity.value ?? 0) / 24)!, UCUMUnits.Day),
267+
//
268+
// ("mi", DateTimePrecision.Year) =>
269+
// new CqlQuantity(Math.Truncate((((quantity.value ?? 0) / 60) / 24) / 365)!, UCUMUnits.Year),
270+
// ("mi", DateTimePrecision.Month) =>
271+
// new CqlQuantity(Math.Truncate((((quantity.value ?? 0) / 60) / 24) / 30)!, UCUMUnits.Month),
272+
// ("mi", DateTimePrecision.Day) =>
273+
// new CqlQuantity(Math.Truncate(((quantity.value ?? 0) / 60) / 24)!, UCUMUnits.Day),
274+
// ("mi", DateTimePrecision.Hour) =>
275+
// new CqlQuantity(Math.Truncate((quantity.value ?? 0) / 60)!, UCUMUnits.Hour),
276+
//
277+
// ("s", DateTimePrecision.Year) =>
278+
// new CqlQuantity(Math.Truncate(((((quantity.value ?? 0) / 60) / 60) / 24) / 365)!, UCUMUnits.Year),
279+
// ("s", DateTimePrecision.Month) =>
280+
// new CqlQuantity(Math.Truncate(((((quantity.value ?? 0) / 60) / 60) / 24) / 30)!, UCUMUnits.Month),
281+
// ("s", DateTimePrecision.Day) =>
282+
// new CqlQuantity(Math.Truncate((((quantity.value ?? 0) / 60) / 60) / 24)!, UCUMUnits.Day),
283+
// ("s", DateTimePrecision.Hour) =>
284+
// new CqlQuantity(Math.Truncate(((quantity.value ?? 0) / 60) / 60)!, UCUMUnits.Hour),
285+
// ("s", DateTimePrecision.Minute) =>
286+
// new CqlQuantity(Math.Truncate((quantity.value ?? 0) / 60)!, UCUMUnits.Minute),
287+
//
288+
// ("ms", DateTimePrecision.Year) =>
289+
// new CqlQuantity(Math.Truncate((((((quantity.value ?? 0) / 1000) / 60) / 60) / 24) / 365)!, UCUMUnits.Year),
290+
// ("ms", DateTimePrecision.Month) =>
291+
// new CqlQuantity(Math.Truncate((((((quantity.value ?? 0) / 1000) / 60) / 60) / 24) / 30)!, UCUMUnits.Month),
292+
// ("ms", DateTimePrecision.Day) =>
293+
// new CqlQuantity(Math.Truncate(((((quantity.value ?? 0) / 1000) / 60) / 60) / 24)!, UCUMUnits.Day),
294+
// ("ms", DateTimePrecision.Hour) =>
295+
// new CqlQuantity(Math.Truncate((((quantity.value ?? 0) / 1000) / 60) / 60)!, UCUMUnits.Hour),
296+
// ("ms", DateTimePrecision.Minute) =>
297+
// new CqlQuantity(Math.Truncate(((quantity.value ?? 0) / 1000) / 60)!, UCUMUnits.Minute),
298+
// ("ms", DateTimePrecision.Second) =>
299+
// new CqlQuantity(Math.Truncate((quantity.value ?? 0) / 1000)!, UCUMUnits.Second),
300+
// (_,_) => quantity
301+
// };
302+
// }
285303
}
286304
}

0 commit comments

Comments
 (0)