KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_text_eval_parser_datetime.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.TXT for contributors.
5 *
6 * This program is free softwar { "@{datestring('2023年12月25日')}", "19716", false }, // Christmas 2023 { "@{datestring('2023年12月25日')}", "19716", false }, // Christmas 2023 { "@{datestring('2023年12월25일')}", "19716", false }, // Christmas 2023; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
24
27
28#include <chrono>
29#include <regex>
30
31BOOST_AUTO_TEST_SUITE( TextEvalParserDateTime )
32
33BOOST_AUTO_TEST_CASE( DateFormatting )
34{
35 EXPRESSION_EVALUATOR evaluator;
36
37 struct TestCase {
38 std::string expression;
39 std::string expected;
40 bool shouldError;
41 };
42
43 const std::vector<TestCase> cases = {
44 // Test Unix epoch date (1970-01-01)
45 { "@{dateformat(0)}", "1970-01-01", false },
46 { "@{dateformat(0, \"ISO\")}", "1970-01-01", false },
47 { "@{dateformat(0, \"iso\")}", "1970-01-01", false },
48 { "@{dateformat(0, \"US\")}", "01/01/1970", false },
49 { "@{dateformat(0, \"us\")}", "01/01/1970", false },
50 { "@{dateformat(0, \"EU\")}", "01/01/1970", false },
51 { "@{dateformat(0, \"european\")}", "01/01/1970", false },
52 { "@{dateformat(0, \"long\")}", "January 1, 1970", false },
53 { "@{dateformat(0, \"short\")}", "Jan 1, 1970", false },
54
55 // Test some known dates
56 { "@{dateformat(365)}", "1971-01-01", false }, // One year after epoch
57 { "@{dateformat(1000)}", "1972-09-27", false }, // 1000 days after epoch
58
59 // Test weekday names
60 { "@{weekdayname(0)}", "Thursday", false }, // Unix epoch was Thursday
61 { "@{weekdayname(1)}", "Friday", false }, // Next day
62 { "@{weekdayname(2)}", "Saturday", false }, // Weekend
63 { "@{weekdayname(3)}", "Sunday", false },
64 { "@{weekdayname(4)}", "Monday", false },
65 { "@{weekdayname(5)}", "Tuesday", false },
66 { "@{weekdayname(6)}", "Wednesday", false },
67 { "@{weekdayname(7)}", "Thursday", false }, // Week cycles
68
69 // Test negative dates (before epoch)
70 { "@{dateformat(-1)}", "1969-12-31", false },
71 { "@{weekdayname(-1)}", "Wednesday", false },
72 };
73
74 for( const auto& testCase : cases )
75 {
76 auto result = evaluator.Evaluate( wxString::FromUTF8( testCase.expression ) );
77
78 if( testCase.shouldError )
79 {
80 BOOST_CHECK( evaluator.HasErrors() );
81 }
82 else
83 {
84 BOOST_CHECK_MESSAGE( !evaluator.HasErrors(),
85 "Error in expression: " + testCase.expression +
86 " Errors: " + evaluator.GetErrorSummary().ToStdString() );
87 BOOST_CHECK_EQUAL( result.ToStdString( wxConvUTF8 ), testCase.expected );
88 }
89 }
90}
91
95BOOST_AUTO_TEST_CASE( CJKDateFormatting )
96{
97 EXPRESSION_EVALUATOR evaluator;
98
99 struct TestCase {
100 std::string expression;
101 std::string expected;
102 bool shouldError;
103 };
104
105 const std::vector<TestCase> cases = {
106 // Test Unix epoch date (1970-01-01) in CJK formats
107 { "@{dateformat(0, \"Chinese\")}", "1970年01月01日", false },
108 { "@{dateformat(0, \"chinese\")}", "1970年01月01日", false },
109 { "@{dateformat(0, \"CN\")}", "1970年01月01日", false },
110 { "@{dateformat(0, \"cn\")}", "1970年01月01日", false },
111
112 { "@{dateformat(0, \"Japanese\")}", "1970年01月01日", false },
113 { "@{dateformat(0, \"japanese\")}", "1970年01月01日", false },
114 { "@{dateformat(0, \"JP\")}", "1970年01月01日", false },
115 { "@{dateformat(0, \"jp\")}", "1970年01月01日", false },
116
117 { "@{dateformat(0, \"Korean\")}", "1970년 01월 01일", false },
118 { "@{dateformat(0, \"korean\")}", "1970년 01월 01일", false },
119 { "@{dateformat(0, \"KR\")}", "1970년 01월 01일", false },
120 { "@{dateformat(0, \"kr\")}", "1970년 01월 01일", false },
121
122 // Test some other dates in CJK formats
123 { "@{dateformat(365, \"Chinese\")}", "1971年01月01日", false }, // One year after epoch
124 { "@{dateformat(365, \"Japanese\")}", "1971年01月01日", false }, // One year after epoch
125 { "@{dateformat(365, \"Korean\")}", "1971년 01월 01일", false }, // One year after epoch
126
127 { "@{dateformat(1000, \"Chinese\")}", "1972年09月27日", false }, // 1000 days after epoch
128 { "@{dateformat(1000, \"Japanese\")}", "1972年09月27日", false }, // 1000 days after epoch
129 { "@{dateformat(1000, \"Korean\")}", "1972년 09월 27일", false }, // 1000 days after epoch
130
131 // Test negative dates (before epoch) in CJK formats
132 { "@{dateformat(-1, \"Chinese\")}", "1969年12月31日", false },
133 { "@{dateformat(-1, \"Japanese\")}", "1969年12月31日", false },
134 { "@{dateformat(-1, \"Korean\")}", "1969년 12월 31일", false },
135
136 // Test leap year date (Feb 29, 1972) in CJK formats
137 { "@{dateformat(789, \"Chinese\")}", "1972年02月29日", false }, // Feb 29, 1972 (leap year)
138 { "@{dateformat(789, \"Japanese\")}", "1972年02月29日", false }, // Feb 29, 1972 (leap year)
139 { "@{dateformat(789, \"Korean\")}", "1972년 02월 29일", false }, // Feb 29, 1972 (leap year)
140 };
141
142 for( const auto& testCase : cases )
143 {
144 auto result = evaluator.Evaluate( wxString::FromUTF8( testCase.expression ) );
145
146 if( testCase.shouldError )
147 {
148 BOOST_CHECK( evaluator.HasErrors() );
149 }
150 else
151 {
152 BOOST_CHECK_MESSAGE( !evaluator.HasErrors(),
153 "Error in expression: " + testCase.expression +
154 " Errors: " + evaluator.GetErrorSummary().ToStdString() );
155 BOOST_CHECK_EQUAL( result.ToStdString( wxConvUTF8 ), testCase.expected );
156 }
157 }
158}
159
163BOOST_AUTO_TEST_CASE( CJKDateParsing )
164{
165 EXPRESSION_EVALUATOR evaluator;
166
167 struct TestCase {
168 std::string expression;
169 std::string expected;
170 bool shouldError;
171 };
172
173 const std::vector<TestCase> cases = {
174 // Test if basic functions work first
175 { "@{dateformat(0)}", "1970-01-01", false }, // Test basic dateformat
176 { "@{upper(\"test\")}", "TEST", false }, // Test basic string function
177
178 // Test ASCII date parsing first to see if datestring function works
179 { "@{datestring(\"2024-03-15\")}", "19797", false }, // Test ASCII date
180 { "@{datestring(\"1970-01-01\")}", "0", false }, // Unix epoch
181
182 // Test Chinese date parsing (年月日)
183 { "@{datestring('2024年03月15日')}", "19797", false }, // Days since epoch for 2024-03-15
184 { "@{datestring('1970年01月01日')}", "0", false }, // Unix epoch
185 { "@{datestring('2024年01月01日')}", "19723", false }, // New Year 2024
186 { "@{datestring('1972年02月29日')}", "789", false }, // Leap year date
187 { "@{datestring('1969年12月31日')}", "-1", false }, // Day before epoch
188
189 // Test Korean date parsing (년월일) with spaces
190 { "@{datestring(\"2024년 03월 15일\")}", "19797", false }, // Days since epoch for 2024-03-15
191 { "@{datestring(\"1970년 01월 01일\")}", "0", false }, // Unix epoch
192 { "@{datestring(\"2024년 01월 01일\")}", "19723", false }, // New Year 2024
193 { "@{datestring(\"1972년 02월 29일\")}", "789", false }, // Leap year date
194 { "@{datestring(\"1969년 12월 31일\")}", "-1", false }, // Day before epoch
195
196 // Test Korean date parsing (년월일) without spaces
197 { "@{datestring(\"2024년03월15일\")}", "19797", false }, // Days since epoch for 2024-03-15
198 { "@{datestring(\"1970년01월01일\")}", "0", false }, // Unix epoch
199
200 // Test integration: parse CJK date and format in different style
201 { "@{dateformat(datestring('2024年03월15일'), 'ISO')}", "2024-03-15", false },
202 { "@{dateformat(datestring('2024년 03월 15일'), 'ISO')}", "2024-03-15", false },
203 { "@{dateformat(datestring('1970年01月01日'), 'US')}", "01/01/1970", false },
204 { "@{dateformat(datestring('1970년 01월 01일'), 'EU')}", "01/01/1970", false },
205
206 // Test round-trip: CJK -> parse -> format back to CJK
207 { "@{dateformat(datestring('2024年03월15日'), 'Chinese')}", "2024年03月15日", false },
208 { "@{dateformat(datestring('2024년 03월 15일'), 'Korean')}", "2024년 03월 15일", false },
209
210 // Test invalid CJK dates (should error)
211 { "@{datestring('2024年13月15日')}", "", true }, // Invalid month
212 { "@{datestring('2024년 02월 30일')}", "", true }, // Invalid day for February
213 { "@{datestring('2024年02月')}", "", true }, // Missing day
214 { "@{datestring('2024년')}", "", true }, // Missing month and day
215 };
216
217 for( const auto& testCase : cases )
218 {
219 auto result = evaluator.Evaluate( wxString::FromUTF8( testCase.expression ) );
220
221 if( testCase.shouldError )
222 {
223 BOOST_CHECK_MESSAGE( evaluator.HasErrors(),
224 "Expected error but got result: " +
225 result.ToStdString( wxConvUTF8 ) +
226 " for expression: " + testCase.expression );
227 }
228 else
229 {
230 BOOST_CHECK_MESSAGE( !evaluator.HasErrors(),
231 "Error in expression: " + testCase.expression +
232 " Errors: " + evaluator.GetErrorSummary().ToStdString() );
233 BOOST_CHECK_EQUAL( result.ToStdString( wxConvUTF8 ), testCase.expected );
234 }
235 }
236}
237
241BOOST_AUTO_TEST_CASE( CurrentDateTime )
242{
243 EXPRESSION_EVALUATOR evaluator;
244
245 auto todayResult = evaluator.Evaluate( "@{today()}" );
246 BOOST_CHECK( !evaluator.HasErrors() );
247
248 // Should return a number (days since epoch)
249 double todayDays = std::stod( todayResult.ToStdString() );
250
251 // Should be a reasonable number of days since 1970
252 // As of 2024, this should be over 19,000 days
253 BOOST_CHECK_GT( todayDays, 19000 );
254 BOOST_CHECK_LT( todayDays, 50000 ); // Reasonable upper bound
255
256 // Test now() function
257 auto nowResult = evaluator.Evaluate( "@{now()}" );
258 BOOST_CHECK( !evaluator.HasErrors() );
259
260 // Should return a timestamp (seconds since epoch)
261 double nowTimestamp = std::stod( nowResult.ToStdString() );
262
263 // Should be a reasonable timestamp
264 auto currentTime = std::chrono::system_clock::now();
265 auto currentTimestamp = std::chrono::system_clock::to_time_t( currentTime );
266 double currentTimestampDouble = static_cast<double>( currentTimestamp );
267
268 // Should be within a few seconds of current time
269 BOOST_CHECK_CLOSE( nowTimestamp, currentTimestampDouble, 1.0 ); // Within 1%
270
271 // Test that consecutive calls to today() return the same value
272 auto todayResult2 = evaluator.Evaluate( "@{today()}" );
273 BOOST_CHECK_EQUAL( todayResult, todayResult2 );
274
275 // Test formatting current date
276 auto formattedToday = evaluator.Evaluate( "@{dateformat(today(), \"ISO\")}" );
277 BOOST_CHECK( !evaluator.HasErrors() );
278
279 // Should be in ISO format: YYYY-MM-DD
280 std::regex isoDateRegex( R"(\d{4}-\d{2}-\d{2})" );
281 BOOST_CHECK( std::regex_match( formattedToday.ToStdString(), isoDateRegex ) );
282}
283
287BOOST_AUTO_TEST_CASE( DateArithmetic )
288{
289 EXPRESSION_EVALUATOR evaluator;
290
291 struct TestCase {
292 std::string expression;
293 std::string expected;
294 bool shouldError;
295 };
296
297 const std::vector<TestCase> cases = {
298 // Date arithmetic
299 { "@{dateformat(0 + 1)}", "1970-01-02", false }, // Add one day
300 { "@{dateformat(0 + 7)}", "1970-01-08", false }, // Add one week
301 { "@{dateformat(0 + 30)}", "1970-01-31", false }, // Add 30 days
302 { "@{dateformat(0 + 365)}", "1971-01-01", false }, // Add one year (1970 was not leap)
303
304 // Leap year test
305 { "@{dateformat(365 + 365 + 366)}", "1973-01-01", false }, // 1972 was leap year
306
307 // Date differences
308 { "@{365 - 0}", "365", false }, // Days between dates
309
310 // Complex date expressions
311 { "@{weekdayname(today())}", "", false }, // Should return a weekday name (we can't predict which)
312 };
313
314 for( const auto& testCase : cases )
315 {
316 auto result = evaluator.Evaluate( wxString::FromUTF8( testCase.expression ) );
317
318 if( testCase.shouldError )
319 {
320 BOOST_CHECK( evaluator.HasErrors() );
321 }
322 else
323 {
324 BOOST_CHECK( !evaluator.HasErrors() );
325
326 if( !testCase.expected.empty() )
327 {
328 BOOST_CHECK_EQUAL( result.ToStdString( wxConvUTF8 ), testCase.expected );
329 }
330 else
331 {
332 // For dynamic results like weekday names, just check it's not empty
333 BOOST_CHECK( !result.empty() );
334 }
335 }
336 }
337}
338
342BOOST_AUTO_TEST_CASE( DateEdgeCases )
343{
344 EXPRESSION_EVALUATOR evaluator;
345
346 struct TestCase {
347 std::string expression;
348 std::string expected;
349 bool shouldError;
350 };
351
352 const std::vector<TestCase> cases = {
353 // Leap year boundaries
354 { "@{dateformat(365 + 365 + 59)}", "1972-02-29", false }, // Feb 29, 1972 (leap year)
355 { "@{dateformat(365 + 365 + 60)}", "1972-03-01", false }, // Mar 1, 1972
356
357 // Year boundaries
358 { "@{dateformat(365 - 1)}", "1970-12-31", false }, // Last day of 1970
359 { "@{dateformat(365)}", "1971-01-01", false }, // First day of 1971
360
361 // Month boundaries
362 { "@{dateformat(30)}", "1970-01-31", false }, // Last day of January
363 { "@{dateformat(31)}", "1970-02-01", false }, // First day of February
364 { "@{dateformat(58)}", "1970-02-28", false }, // Last day of February 1970 (not leap)
365 { "@{dateformat(59)}", "1970-03-01", false }, // First day of March 1970
366
367 // Large date values
368 { "@{dateformat(36525)}", "2070-01-01", false }, // 100 years after epoch
369
370 // Negative dates (before epoch)
371 { "@{dateformat(-365)}", "1969-01-01", false }, // One year before epoch
372 { "@{dateformat(-1)}", "1969-12-31", false }, // One day before epoch
373
374 // Weekday wrap-around
375 { "@{weekdayname(-1)}", "Wednesday", false }, // Day before Thursday
376 { "@{weekdayname(-7)}", "Thursday", false }, // One week before
377
378 // Edge case: very large weekday values
379 { "@{weekdayname(7000)}", "Thursday", false }, // Should still work
380 };
381
382 for( const auto& testCase : cases )
383 {
384 auto result = evaluator.Evaluate( wxString::FromUTF8( testCase.expression ) );
385
386 if( testCase.shouldError )
387 {
388 BOOST_CHECK( evaluator.HasErrors() );
389 }
390 else
391 {
392 BOOST_CHECK_MESSAGE( !evaluator.HasErrors(),
393 "Error in expression: " + testCase.expression );
394 BOOST_CHECK_EQUAL( result.ToStdString( wxConvUTF8 ), testCase.expected );
395 }
396 }
397}
398
402BOOST_AUTO_TEST_CASE( DateFormattingMixed )
403{
404 EXPRESSION_EVALUATOR evaluator;
405 evaluator.SetVariable( "days_offset", 100.0 );
406
407 struct TestCase {
408 std::string expression;
409 bool shouldWork;
410 };
411
412 const std::vector<TestCase> cases = {
413 // Complex expressions combining dates and variables
414 { "Today is @{dateformat(today())} which is @{weekdayname(today())}", true },
415 { "Date: @{dateformat(0 + ${days_offset}, \"long\")}", true },
416 { "In @{format(${days_offset})} days: @{dateformat(today() + ${days_offset})}", true },
417
418 // Nested function calls
419 { "@{upper(weekdayname(today()))}", true },
420 { "@{lower(dateformat(today(), \"long\"))}", true },
421
422 // Multiple date calculations
423 { "Start: @{dateformat(0)} End: @{dateformat(365)} Duration: @{365 - 0} days", true },
424 };
425
426 for( const auto& testCase : cases )
427 {
428 auto result = evaluator.Evaluate( wxString::FromUTF8( testCase.expression ) );
429
430 if( testCase.shouldWork )
431 {
432 BOOST_CHECK_MESSAGE( !evaluator.HasErrors(),
433 "Error in expression: " + testCase.expression +
434 " Result: " + result.ToStdString( wxConvUTF8 ) );
435 BOOST_CHECK( !result.empty() );
436 }
437 else
438 {
439 BOOST_CHECK( evaluator.HasErrors() );
440 }
441 }
442}
443
447BOOST_AUTO_TEST_CASE( DatePerformance )
448{
449 EXPRESSION_EVALUATOR evaluator;
450
451 // Test that date operations are reasonably fast
452 auto start = std::chrono::high_resolution_clock::now();
453
454 // Perform many date operations
455 for( int i = 0; i < 1000; ++i )
456 {
457 auto result = evaluator.Evaluate( "@{dateformat(" + std::to_string(i) + ")}" );
458 BOOST_CHECK( !evaluator.HasErrors() );
459 }
460
461 auto end = std::chrono::high_resolution_clock::now();
462 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>( end - start );
463
464 // Should complete in reasonable time (less than 100 milliseconds for 1000 operations)
465 BOOST_CHECK_LT( duration.count(), 100 );
466}
467
High-level wrapper for evaluating mathematical and string expressions in wxString format.
wxString Evaluate(const wxString &aInput)
Main evaluation function - processes input string and evaluates all} expressions.
bool HasErrors() const
Check if the last evaluation had errors.
wxString GetErrorSummary() const
Get detailed error information from the last evaluation.
void SetVariable(const wxString &aName, double aValue)
Set a numeric variable for use in expressions.
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_SUITE_END()
VECTOR3I expected(15, 30, 45)
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
VECTOR2I end
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")
BOOST_AUTO_TEST_CASE(DateFormatting)