1
- using System . Linq . Expressions ;
1
+ /*
2
+ * Copyright (c) 2025, Firely, NCQA and contributors
3
+ * See the file CONTRIBUTORS for details.
4
+ *
5
+ * This file is licensed under the BSD 3-Clause license
6
+ * available at https://raw.githubusercontent.com/FirelyTeam/firely-cql-sdk/main/LICENSE
7
+ */
8
+
9
+ #nullable enable
10
+ using System . Linq . Expressions ;
11
+ using Hl7 . Cql . Fhir ;
2
12
using Hl7 . Cql . Iso8601 ;
3
13
using Hl7 . Cql . Operators ;
4
14
using Hl7 . Cql . Primitives ;
15
+ using Hl7 . Cql . Runtime ;
5
16
6
17
namespace CoreTests ;
7
18
19
+ [ TestClass ]
20
+ [ TestCategory ( "UnitTest" ) ]
21
+ public class CqlDateTimeTests
22
+ {
23
+ private CqlContext GetNewContext ( ) => FhirCqlContext . WithDataSource ( ) ;
24
+
25
+ [ TestMethod ]
26
+ public void CqlDateTime_Add_Year_By_Units ( )
27
+ {
28
+ Assert . IsTrue ( CqlDateTime . TryParse ( "1960" , out var baseDate ) ) ;
29
+ Assert . AreEqual ( DateTimePrecision . Year , baseDate . Value . Precision ) ;
30
+ var plusOneYear = baseDate . Add ( new CqlQuantity ( 1m , "year" ) ) ;
31
+ Assert . AreEqual ( DateTimePrecision . Year , plusOneYear . Value . Precision ) ;
32
+ Assert . IsNull ( plusOneYear . Value . Month ) ;
33
+ Assert . AreEqual ( "1961" , plusOneYear . ToString ( ) ) ;
34
+
35
+ var plusTwelveMonths = baseDate . Add ( new CqlQuantity ( 12m , "month" ) ) ;
36
+ Assert . AreEqual ( DateTimePrecision . Year , plusTwelveMonths . Value . Precision ) ;
37
+ Assert . IsNull ( plusTwelveMonths . Value . Month ) ;
38
+ Assert . AreEqual ( "1961" , plusTwelveMonths . ToString ( ) ) ;
39
+
40
+ var plus365days = baseDate . Add ( new CqlQuantity ( 365 , "day" ) ) ;
41
+ Assert . AreEqual ( DateTimePrecision . Year , plus365days . Value . Precision ) ;
42
+ Assert . IsNull ( plus365days . Value . Month ) ;
43
+ Assert . AreEqual ( "1960" , plus365days . ToString ( ) ) ;
44
+
45
+ var plus366days = baseDate . Add ( new CqlQuantity ( 366 , "day" ) ) ;
46
+ Assert . AreEqual ( DateTimePrecision . Year , plus366days . Value . Precision ) ;
47
+ Assert . IsNull ( plus366days . Value . Month ) ;
48
+ Assert . AreEqual ( "1961" , plus366days . ToString ( ) ) ;
49
+
50
+ var plus366DaysInHours = baseDate . Add ( new CqlQuantity ( 366 * 24 , "hours" ) ) ;
51
+ Assert . AreEqual ( DateTimePrecision . Year , plus366DaysInHours . Value . Precision ) ;
52
+ Assert . IsNull ( plus366DaysInHours . Value . Month ) ;
53
+ Assert . AreEqual ( "1961" , plus366DaysInHours . ToString ( ) ) ;
54
+
55
+ var plus365DaysInSeconds = baseDate . Add ( new CqlQuantity ( 365 * 24 * 60 * 60 , "seconds" ) ) ;
56
+ Assert . AreEqual ( DateTimePrecision . Year , plus365DaysInSeconds . Value . Precision ) ;
57
+ Assert . IsNull ( plus365DaysInSeconds . Value . Month ) ;
58
+ Assert . AreEqual ( "1960" , plus365DaysInSeconds . ToString ( ) ) ;
59
+ }
60
+
61
+ [ TestMethod ]
62
+ public void CqlDateTime_Add_Month ( )
63
+ {
64
+ Assert . IsTrue ( CqlDateTime . TryParse ( "2022-01-01" , out var baseDate ) ) ;
65
+
66
+ var plus1Month = baseDate . Add ( new CqlQuantity ( 1m , "month" ) ) ;
67
+ Assert . AreEqual ( DateTimePrecision . Day , plus1Month . Value . Precision ) ;
68
+ Assert . IsNull ( plus1Month . Value . Hour ) ;
69
+ Assert . AreEqual ( "2022-02-01" , plus1Month . ToString ( ) ) ;
70
+
71
+ var plus2Months = baseDate . Add ( new CqlQuantity ( 2m , "month" ) ) ;
72
+ Assert . AreEqual ( DateTimePrecision . Day , plus2Months . Value . Precision ) ;
73
+ Assert . IsNull ( plus2Months . Value . Hour ) ;
74
+ Assert . AreEqual ( "2022-03-01" , plus2Months . ToString ( ) ) ;
75
+
76
+ var plus2pt5Months = baseDate . Add ( new CqlQuantity ( 2.5m , "month" ) ) ;
77
+ Assert . AreEqual ( DateTimePrecision . Day , plus2pt5Months . Value . Precision ) ;
78
+ Assert . IsNull ( plus2pt5Months . Value . Hour ) ;
79
+ Assert . AreEqual ( "2022-03-01" , plus2pt5Months . ToString ( ) ) ;
80
+
81
+ var plus1UcumMonth = baseDate . Add ( new CqlQuantity ( 1m , "mo" ) ) ;
82
+ Assert . AreEqual ( DateTimePrecision . Day , plus1UcumMonth . Value . Precision ) ;
83
+ Assert . IsNull ( plus1UcumMonth . Value . Hour ) ;
84
+ Assert . AreEqual ( "2022-01-31" , plus1UcumMonth . ToString ( ) ) ;
85
+
86
+ }
87
+
88
+ [ TestMethod ]
89
+ public void CqlDateTime_Subtract_Month ( )
90
+ {
91
+ Assert . IsTrue ( CqlDateTime . TryParse ( "2022-03-01" , out var baseDate ) ) ;
92
+
93
+ var minus1Month = baseDate . Subtract ( new CqlQuantity ( 1m , "month" ) ) ;
94
+ Assert . AreEqual ( DateTimePrecision . Day , minus1Month . Value . Precision ) ;
95
+ Assert . IsNull ( minus1Month . Value . Hour ) ;
96
+ Assert . AreEqual ( "2022-02-01" , minus1Month . ToString ( ) ) ;
97
+
98
+ var minus2Months = baseDate . Subtract ( new CqlQuantity ( 2m , "month" ) ) ;
99
+ Assert . AreEqual ( DateTimePrecision . Day , minus2Months . Value . Precision ) ;
100
+ Assert . IsNull ( minus2Months . Value . Hour ) ;
101
+ Assert . AreEqual ( "2022-01-01" , minus2Months . ToString ( ) ) ;
102
+
103
+ var minus2pt5Months = baseDate . Subtract ( new CqlQuantity ( 2.5m , "month" ) ) ;
104
+ Assert . AreEqual ( DateTimePrecision . Day , minus2pt5Months . Value . Precision ) ;
105
+ Assert . IsNull ( minus2pt5Months . Value . Hour ) ;
106
+ Assert . AreEqual ( "2022-01-01" , minus2pt5Months . ToString ( ) ) ;
107
+
108
+ var minus1UcumMonth = baseDate . Subtract ( new CqlQuantity ( 1m , "mo" ) ) ;
109
+ Assert . AreEqual ( DateTimePrecision . Day , minus1UcumMonth . Value . Precision ) ;
110
+ Assert . IsNull ( minus1UcumMonth . Value . Hour ) ;
111
+ Assert . AreEqual ( "2022-01-29" , minus1UcumMonth . ToString ( ) ) ;
112
+
113
+ }
114
+
115
+ [ TestMethod ]
116
+ public void CqlDateTime_Subtract_Year ( )
117
+ {
118
+ Assert . IsTrue ( CqlDateTime . TryParse ( "2025-03-01" , out var baseDate ) ) ;
119
+
120
+ var minus1Year = baseDate . Subtract ( new CqlQuantity ( 1m , "year" ) ) ;
121
+ Assert . AreEqual ( DateTimePrecision . Day , minus1Year . Value . Precision ) ;
122
+ Assert . IsNull ( minus1Year . Value . Hour ) ;
123
+ Assert . AreEqual ( "2024-03-01" , minus1Year . ToString ( ) ) ;
124
+
125
+ var minus1UcumYear = baseDate . Subtract ( new CqlQuantity ( 1m , "a" ) ) ;
126
+ Assert . AreEqual ( DateTimePrecision . Day , minus1UcumYear . Value . Precision ) ;
127
+ Assert . IsNull ( minus1UcumYear . Value . Hour ) ;
128
+ Assert . AreEqual ( "2024-02-29" , minus1UcumYear . ToString ( ) ) ;
129
+
130
+ }
131
+
132
+ [ TestMethod ]
133
+ public void CqlDateTime_Subtract_Day_and_Days ( )
134
+ {
135
+ var threeDays = new CqlQuantity ( 3 , "days" ) ;
136
+ var oneDay = new CqlQuantity ( 1 , "day" ) ;
137
+ var method = typeof ( ICqlOperators )
138
+ . GetMethods ( )
139
+ . Where ( x =>
140
+ x . Name == nameof ( CqlOperators . Subtract ) &&
141
+ x . GetParameters ( ) . Count ( ) == 2 &&
142
+ x . GetParameters ( ) [ 0 ] . ParameterType == typeof ( CqlQuantity ) &&
143
+ x . GetParameters ( ) [ 1 ] . ParameterType == typeof ( CqlQuantity )
144
+ ) . First ( ) ;
145
+
146
+
147
+ var tdExpr = Expression . Constant ( threeDays ) ;
148
+ var odExpr = Expression . Constant ( oneDay ) ;
149
+
150
+ var rc = GetNewContext ( ) ;
151
+ var fcq = rc . Operators ;
152
+ var memExpr = Expression . Constant ( fcq ) ;
153
+
154
+ var call = Expression . Call ( memExpr , method , tdExpr , odExpr ) ;
155
+ var le = Expression . Lambda < Func < CqlQuantity > > ( call ) ;
156
+ var compiled = le . Compile ( ) ;
157
+ var result = compiled . Invoke ( ) ;
158
+
159
+
160
+ }
161
+
162
+ [ TestMethod ]
163
+ public void CqlDateTime_BoundariesBetween_Months ( )
164
+ {
165
+ Assert . IsTrue ( DateTimeIso8601 . TryParse ( "2020-02-29" , out var startDate ) ) ;
166
+ Assert . IsTrue ( CqlDateTime . TryParse ( "2020-04-01" , out var cqlStartDate ) ) ;
167
+ Assert . IsTrue ( CqlDateTime . TryParse ( "2020-03-31" , out var cqlEndDate ) ) ;
168
+ var boundariesBetween = new CqlDateTime ( startDate ) . BoundariesBetween ( cqlStartDate , "month" ) ;
169
+ Assert . AreEqual ( 2 , boundariesBetween ) ;
170
+ boundariesBetween = new CqlDateTime ( startDate ) . BoundariesBetween ( cqlEndDate , "month" ) ;
171
+ Assert . AreEqual ( 1 , boundariesBetween ) ;
172
+
173
+ Assert . IsTrue ( DateTimeIso8601 . TryParse ( "2020-03-01" , out startDate ) ) ;
174
+ Assert . IsTrue ( CqlDateTime . TryParse ( "2020-04-30" , out cqlStartDate ) ) ;
175
+ Assert . IsTrue ( CqlDateTime . TryParse ( "2020-03-31" , out cqlEndDate ) ) ;
176
+ boundariesBetween = new CqlDateTime ( startDate ) . BoundariesBetween ( cqlStartDate , "month" ) ;
177
+ Assert . AreEqual ( 1 , boundariesBetween ) ;
178
+
179
+ boundariesBetween = new CqlDateTime ( startDate ) . BoundariesBetween ( cqlEndDate , "month" ) ;
180
+ Assert . AreEqual ( 0 , boundariesBetween ) ;
181
+ }
182
+ [ TestMethod ]
183
+ public void CqlDateTime_BoundariesBetween_Years ( )
184
+ {
185
+ Assert . IsTrue ( DateTimeIso8601 . TryParse ( "2020-02-29" , out var startDate ) ) ;
186
+ Assert . IsTrue ( CqlDateTime . TryParse ( "2021-02-28" , out var cqlStartDate ) ) ;
187
+ var boundariesBetween = new CqlDateTime ( startDate ) . BoundariesBetween ( cqlStartDate , "year" ) ;
188
+ Assert . AreEqual ( 1 , boundariesBetween ) ;
189
+
190
+ Assert . IsTrue ( CqlDateTime . TryParse ( "2022-01-01" , out cqlStartDate ) ) ;
191
+ boundariesBetween = new CqlDateTime ( startDate ) . BoundariesBetween ( cqlStartDate , "year" ) ;
192
+ Assert . AreEqual ( 2 , boundariesBetween ) ;
193
+
194
+ Assert . IsTrue ( CqlDateTime . TryParse ( "2020-03-31" , out cqlStartDate ) ) ;
195
+ boundariesBetween = new CqlDateTime ( startDate ) . BoundariesBetween ( cqlStartDate , "year" ) ;
196
+ Assert . AreEqual ( 0 , boundariesBetween ) ;
197
+ }
198
+
199
+ [ TestMethod ]
200
+ public void CqlDateTime_WholeCalendarPeriodsBetween_Years ( )
201
+ {
202
+ Assert . IsTrue ( DateTimeIso8601 . TryParse ( "2020-02-29" , out var startDate ) ) ;
203
+ Assert . IsTrue ( CqlDateTime . TryParse ( "2020-06-30" , out var cqlStartDate ) ) ;
204
+
205
+ var boundariesBetween = new CqlDateTime ( startDate ) . WholeCalendarPeriodsBetween ( cqlStartDate , "year" ) ;
206
+ Assert . AreEqual ( 0 , boundariesBetween ) ;
207
+
208
+ Assert . IsTrue ( CqlDateTime . TryParse ( "2021-02-28" , out cqlStartDate ) ) ;
209
+ boundariesBetween = new CqlDateTime ( startDate ) . WholeCalendarPeriodsBetween ( cqlStartDate , "year" ) ;
210
+ Assert . AreEqual ( 0 , boundariesBetween ) ; // 1 full year occurs on mar 1, not feb 28
211
+
212
+ Assert . IsTrue ( CqlDateTime . TryParse ( "2021-03-01" , out cqlStartDate ) ) ;
213
+ boundariesBetween = new CqlDateTime ( startDate ) . WholeCalendarPeriodsBetween ( cqlStartDate , "year" ) ;
214
+ Assert . AreEqual ( 1 , boundariesBetween ) ;
215
+
216
+ Assert . IsTrue ( CqlDateTime . TryParse ( "2021-06-30" , out cqlStartDate ) ) ;
217
+ boundariesBetween = new CqlDateTime ( startDate ) . WholeCalendarPeriodsBetween ( cqlStartDate , "year" ) ;
218
+ Assert . AreEqual ( 1 , boundariesBetween ) ;
219
+
220
+ Assert . IsTrue ( DateTimeIso8601 . TryParse ( "2008-04-11" , out startDate ) ) ;
221
+ Assert . IsTrue ( CqlDateTime . TryParse ( "2024-04-10" , out cqlStartDate ) ) ;
222
+ boundariesBetween = new CqlDateTime ( startDate ) . WholeCalendarPeriodsBetween ( cqlStartDate , "year" ) ;
223
+ Assert . AreEqual ( 15 , boundariesBetween ) ;
224
+
225
+ // leap year
226
+ Assert . IsTrue ( DateTimeIso8601 . TryParse ( "2020-04-11" , out startDate ) ) ;
227
+ Assert . IsTrue ( CqlDateTime . TryParse ( "2023-05-11" , out cqlStartDate ) ) ;
228
+ boundariesBetween = new CqlDateTime ( startDate ) . WholeCalendarPeriodsBetween ( cqlStartDate , "year" ) ;
229
+ Assert . AreEqual ( 3 , boundariesBetween ) ;
230
+
231
+ // leap day
232
+ Assert . IsTrue ( DateTimeIso8601 . TryParse ( "2003-03-01" , out startDate ) ) ;
233
+ Assert . IsTrue ( CqlDateTime . TryParse ( "2024-02-29" , out cqlStartDate ) ) ;
234
+ boundariesBetween = new CqlDateTime ( startDate ) . WholeCalendarPeriodsBetween ( cqlStartDate , "year" ) ;
235
+ Assert . AreEqual ( 20 , boundariesBetween ) ;
236
+ }
237
+
238
+ [ TestMethod ]
239
+ public void CqlDateTime_WholeCalendarPeriodsBetween_Months ( )
240
+ {
241
+ Assert . IsTrue ( DateTimeIso8601 . TryParse ( "2020-02-29" , out var startDate ) ) ;
242
+ Assert . IsTrue ( CqlDateTime . TryParse ( "2020-06-30" , out var cqlStartDate ) ) ;
243
+
244
+ var boundariesBetween = new CqlDateTime ( startDate ) . WholeCalendarPeriodsBetween ( cqlStartDate , "month" ) ;
245
+ Assert . AreEqual ( 4 , boundariesBetween ) ;
246
+
247
+ Assert . IsTrue ( CqlDateTime . TryParse ( "2021-02-28" , out cqlStartDate ) ) ;
248
+ boundariesBetween = new CqlDateTime ( startDate ) . WholeCalendarPeriodsBetween ( cqlStartDate , "month" ) ;
249
+ Assert . AreEqual ( 11 , boundariesBetween ) ; // 1 full year occurs on mar 1, not feb 28
250
+
251
+ Assert . IsTrue ( CqlDateTime . TryParse ( "2021-03-01" , out cqlStartDate ) ) ;
252
+ boundariesBetween = new CqlDateTime ( startDate ) . WholeCalendarPeriodsBetween ( cqlStartDate , "month" ) ;
253
+ Assert . AreEqual ( 12 , boundariesBetween ) ;
254
+
255
+ Assert . IsTrue ( CqlDateTime . TryParse ( "2021-06-30" , out cqlStartDate ) ) ;
256
+ boundariesBetween = new CqlDateTime ( startDate ) . WholeCalendarPeriodsBetween ( cqlStartDate , "month" ) ;
257
+ Assert . AreEqual ( 16 , boundariesBetween ) ;
258
+
259
+ }
260
+ }
0 commit comments