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, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
28
31
32#include <chrono>
33#include <regex>
34
35BOOST_AUTO_TEST_SUITE( TextEvalParserDateTime )
36
37BOOST_AUTO_TEST_CASE( DateFormatting )
38{
39 EXPRESSION_EVALUATOR evaluator;
40
41 struct TestCase {
42 std::string expression;
43 std::string expected;
44 bool shouldError;
45 };
46
47 const std::vector<TestCase> cases = {
48 // Test Unix epoch date (1970-01-01)
49 { "@{dateformat(0)}", "1970-01-01", false },
50 { "@{dateformat(0, \"ISO\")}", "1970-01-01", false },
51 { "@{dateformat(0, \"iso\")}", "1970-01-01", false },
52 { "@{dateformat(0, \"US\")}", "01/01/1970", false },
53 { "@{dateformat(0, \"us\")}", "01/01/1970", false },
54 { "@{dateformat(0, \"EU\")}", "01/01/1970", false },
55 { "@{dateformat(0, \"european\")}", "01/01/1970", false },
56 { "@{dateformat(0, \"long\")}", "January 1, 1970", false },
57 { "@{dateformat(0, \"short\")}", "Jan 1, 1970", false },
58
59 // Test some known dates
60 { "@{dateformat(365)}", "1971-01-01", false }, // One year after epoch
61 { "@{dateformat(1000)}", "1972-09-27", false }, // 1000 days after epoch
62
63 // Test weekday names
64 { "@{weekdayname(0)}", "Thursday", false }, // Unix epoch was Thursday
65 { "@{weekdayname(1)}", "Friday", false }, // Next day
66 { "@{weekdayname(2)}", "Saturday", false }, // Weekend
67 { "@{weekdayname(3)}", "Sunday", false },
68 { "@{weekdayname(4)}", "Monday", false },
69 { "@{weekdayname(5)}", "Tuesday", false },
70 { "@{weekdayname(6)}", "Wednesday", false },
71 { "@{weekdayname(7)}", "Thursday", false }, // Week cycles
72
73 // Test negative dates (before epoch)
74 { "@{dateformat(-1)}", "1969-12-31", false },
75 { "@{weekdayname(-1)}", "Wednesday", false },
76 };
77
78 for( const auto& testCase : cases )
79 {
80 auto result = evaluator.Evaluate( wxString::FromUTF8( testCase.expression ) );
81
82 if( testCase.shouldError )
83 {
84 BOOST_CHECK( evaluator.HasErrors() );
85 }
86 else
87 {
88 BOOST_CHECK_MESSAGE( !evaluator.HasErrors(),
89 "Error in expression: " + testCase.expression +
90 " Errors: " + evaluator.GetErrorSummary().ToStdString() );
91 BOOST_CHECK_EQUAL( result.ToStdString( wxConvUTF8 ), testCase.expected );
92 }
93 }
94}
95
99BOOST_AUTO_TEST_CASE( CJKDateFormatting )
100{
101 EXPRESSION_EVALUATOR evaluator;
102
103 struct TestCase {
104 std::string expression;
105 std::string expected;
106 bool shouldError;
107 };
108
109 const std::vector<TestCase> cases = {
110 // Test Unix epoch date (1970-01-01) in CJK formats
111 { "@{dateformat(0, \"Chinese\")}", "1970年01月01日", false },
112 { "@{dateformat(0, \"chinese\")}", "1970年01月01日", false },
113 { "@{dateformat(0, \"CN\")}", "1970年01月01日", false },
114 { "@{dateformat(0, \"cn\")}", "1970年01月01日", false },
115
116 { "@{dateformat(0, \"Japanese\")}", "1970年01月01日", false },
117 { "@{dateformat(0, \"japanese\")}", "1970年01月01日", false },
118 { "@{dateformat(0, \"JP\")}", "1970年01月01日", false },
119 { "@{dateformat(0, \"jp\")}", "1970年01月01日", false },
120
121 { "@{dateformat(0, \"Korean\")}", "1970년 01월 01일", false },
122 { "@{dateformat(0, \"korean\")}", "1970년 01월 01일", false },
123 { "@{dateformat(0, \"KR\")}", "1970년 01월 01일", false },
124 { "@{dateformat(0, \"kr\")}", "1970년 01월 01일", false },
125
126 // Test some other dates in CJK formats
127 { "@{dateformat(365, \"Chinese\")}", "1971年01月01日", false }, // One year after epoch
128 { "@{dateformat(365, \"Japanese\")}", "1971年01月01日", false }, // One year after epoch
129 { "@{dateformat(365, \"Korean\")}", "1971년 01월 01일", false }, // One year after epoch
130
131 { "@{dateformat(1000, \"Chinese\")}", "1972年09月27日", false }, // 1000 days after epoch
132 { "@{dateformat(1000, \"Japanese\")}", "1972年09月27日", false }, // 1000 days after epoch
133 { "@{dateformat(1000, \"Korean\")}", "1972년 09월 27일", false }, // 1000 days after epoch
134
135 // Test negative dates (before epoch) in CJK formats
136 { "@{dateformat(-1, \"Chinese\")}", "1969年12月31日", false },
137 { "@{dateformat(-1, \"Japanese\")}", "1969年12月31日", false },
138 { "@{dateformat(-1, \"Korean\")}", "1969년 12월 31일", false },
139
140 // Test leap year date (Feb 29, 1972) in CJK formats
141 { "@{dateformat(789, \"Chinese\")}", "1972年02月29日", false }, // Feb 29, 1972 (leap year)
142 { "@{dateformat(789, \"Japanese\")}", "1972年02月29日", false }, // Feb 29, 1972 (leap year)
143 { "@{dateformat(789, \"Korean\")}", "1972년 02월 29일", false }, // Feb 29, 1972 (leap year)
144 };
145
146 for( const auto& testCase : cases )
147 {
148 auto result = evaluator.Evaluate( wxString::FromUTF8( testCase.expression ) );
149
150 if( testCase.shouldError )
151 {
152 BOOST_CHECK( evaluator.HasErrors() );
153 }
154 else
155 {
156 BOOST_CHECK_MESSAGE( !evaluator.HasErrors(),
157 "Error in expression: " + testCase.expression +
158 " Errors: " + evaluator.GetErrorSummary().ToStdString() );
159 BOOST_CHECK_EQUAL( result.ToStdString( wxConvUTF8 ), testCase.expected );
160 }
161 }
162}
163
167BOOST_AUTO_TEST_CASE( CJKDateParsing )
168{
169 EXPRESSION_EVALUATOR evaluator;
170
171 struct TestCase {
172 std::string expression;
173 std::string expected;
174 bool shouldError;
175 };
176
177 const std::vector<TestCase> cases = {
178 // Test if basic functions work first
179 { "@{dateformat(0)}", "1970-01-01", false }, // Test basic dateformat
180 { "@{upper(\"test\")}", "TEST", false }, // Test basic string function
181
182 // Test ASCII date parsing first to see if datestring function works
183 { "@{datestring(\"2024-03-15\")}", "19797", false }, // Test ASCII date
184 { "@{datestring(\"1970-01-01\")}", "0", false }, // Unix epoch
185
186 // Test Chinese date parsing (年月日)
187 { "@{datestring('2024年03月15日')}", "19797", false }, // Days since epoch for 2024-03-15
188 { "@{datestring('1970年01月01日')}", "0", false }, // Unix epoch
189 { "@{datestring('2024年01月01日')}", "19723", false }, // New Year 2024
190 { "@{datestring('1972年02月29日')}", "789", false }, // Leap year date
191 { "@{datestring('1969年12月31日')}", "-1", false }, // Day before epoch
192
193 // Test Korean date parsing (년월일) with spaces
194 { "@{datestring(\"2024년 03월 15일\")}", "19797", false }, // Days since epoch for 2024-03-15
195 { "@{datestring(\"1970년 01월 01일\")}", "0", false }, // Unix epoch
196 { "@{datestring(\"2024년 01월 01일\")}", "19723", false }, // New Year 2024
197 { "@{datestring(\"1972년 02월 29일\")}", "789", false }, // Leap year date
198 { "@{datestring(\"1969년 12월 31일\")}", "-1", false }, // Day before epoch
199
200 // Test Korean date parsing (년월일) without spaces
201 { "@{datestring(\"2024년03월15일\")}", "19797", false }, // Days since epoch for 2024-03-15
202 { "@{datestring(\"1970년01월01일\")}", "0", false }, // Unix epoch
203
204 // Test integration: parse CJK date and format in different style
205 { "@{dateformat(datestring('2024年03월15일'), 'ISO')}", "2024-03-15", false },
206 { "@{dateformat(datestring('2024년 03월 15일'), 'ISO')}", "2024-03-15", false },
207 { "@{dateformat(datestring('1970年01月01日'), 'US')}", "01/01/1970", false },
208 { "@{dateformat(datestring('1970년 01월 01일'), 'EU')}", "01/01/1970", false },
209
210 // Test round-trip: CJK -> parse -> format back to CJK
211 { "@{dateformat(datestring('2024年03월15日'), 'Chinese')}", "2024年03月15日", false },
212 { "@{dateformat(datestring('2024년 03월 15일'), 'Korean')}", "2024년 03월 15일", false },
213
214 // Test invalid CJK dates (should error)
215 { "@{datestring('2024年13月15日')}", "", true }, // Invalid month
216 { "@{datestring('2024년 02월 30일')}", "", true }, // Invalid day for February
217 { "@{datestring('2024年02月')}", "", true }, // Missing day
218 { "@{datestring('2024년')}", "", true }, // Missing month and day
219 };
220
221 for( const auto& testCase : cases )
222 {
223 auto result = evaluator.Evaluate( wxString::FromUTF8( testCase.expression ) );
224
225 if( testCase.shouldError )
226 {
227 BOOST_CHECK_MESSAGE( evaluator.HasErrors(),
228 "Expected error but got result: " +
229 result.ToStdString( wxConvUTF8 ) +
230 " for expression: " + testCase.expression );
231 }
232 else
233 {
234 BOOST_CHECK_MESSAGE( !evaluator.HasErrors(),
235 "Error in expression: " + testCase.expression +
236 " Errors: " + evaluator.GetErrorSummary().ToStdString() );
237 BOOST_CHECK_EQUAL( result.ToStdString( wxConvUTF8 ), testCase.expected );
238 }
239 }
240}
241
245BOOST_AUTO_TEST_CASE( CurrentDateTime )
246{
247 EXPRESSION_EVALUATOR evaluator;
248
249 auto todayResult = evaluator.Evaluate( "@{today()}" );
250 BOOST_CHECK( !evaluator.HasErrors() );
251
252 // Should return a number (days since epoch)
253 double todayDays = std::stod( todayResult.ToStdString() );
254
255 // Should be a reasonable number of days since 1970
256 // As of 2024, this should be over 19,000 days
257 BOOST_CHECK_GT( todayDays, 19000 );
258 BOOST_CHECK_LT( todayDays, 50000 ); // Reasonable upper bound
259
260 // Test now() function
261 auto nowResult = evaluator.Evaluate( "@{now()}" );
262 BOOST_CHECK( !evaluator.HasErrors() );
263
264 // Should return a timestamp (seconds since epoch)
265 double nowTimestamp = std::stod( nowResult.ToStdString() );
266
267 // Should be a reasonable timestamp
268 auto currentTime = std::chrono::system_clock::now();
269 auto currentTimestamp = std::chrono::system_clock::to_time_t( currentTime );
270 double currentTimestampDouble = static_cast<double>( currentTimestamp );
271
272 // Should be within a few seconds of current time
273 BOOST_CHECK_CLOSE( nowTimestamp, currentTimestampDouble, 1.0 ); // Within 1%
274
275 // Test that consecutive calls to today() return the same value
276 auto todayResult2 = evaluator.Evaluate( "@{today()}" );
277 BOOST_CHECK_EQUAL( todayResult, todayResult2 );
278
279 // Test formatting current date
280 auto formattedToday = evaluator.Evaluate( "@{dateformat(today(), \"ISO\")}" );
281 BOOST_CHECK( !evaluator.HasErrors() );
282
283 // Should be in ISO format: YYYY-MM-DD
284 std::regex isoDateRegex( R"(\d{4}-\d{2}-\d{2})" );
285 BOOST_CHECK( std::regex_match( formattedToday.ToStdString(), isoDateRegex ) );
286}
287
291BOOST_AUTO_TEST_CASE( DateArithmetic )
292{
293 EXPRESSION_EVALUATOR evaluator;
294
295 struct TestCase {
296 std::string expression;
297 std::string expected;
298 bool shouldError;
299 };
300
301 const std::vector<TestCase> cases = {
302 // Date arithmetic
303 { "@{dateformat(0 + 1)}", "1970-01-02", false }, // Add one day
304 { "@{dateformat(0 + 7)}", "1970-01-08", false }, // Add one week
305 { "@{dateformat(0 + 30)}", "1970-01-31", false }, // Add 30 days
306 { "@{dateformat(0 + 365)}", "1971-01-01", false }, // Add one year (1970 was not leap)
307
308 // Leap year test
309 { "@{dateformat(365 + 365 + 366)}", "1973-01-01", false }, // 1972 was leap year
310
311 // Date differences
312 { "@{365 - 0}", "365", false }, // Days between dates
313
314 // Complex date expressions
315 { "@{weekdayname(today())}", "", false }, // Should return a weekday name (we can't predict which)
316 };
317
318 for( const auto& testCase : cases )
319 {
320 auto result = evaluator.Evaluate( wxString::FromUTF8( testCase.expression ) );
321
322 if( testCase.shouldError )
323 {
324 BOOST_CHECK( evaluator.HasErrors() );
325 }
326 else
327 {
328 BOOST_CHECK( !evaluator.HasErrors() );
329
330 if( !testCase.expected.empty() )
331 {
332 BOOST_CHECK_EQUAL( result.ToStdString( wxConvUTF8 ), testCase.expected );
333 }
334 else
335 {
336 // For dynamic results like weekday names, just check it's not empty
337 BOOST_CHECK( !result.empty() );
338 }
339 }
340 }
341}
342
346BOOST_AUTO_TEST_CASE( DateEdgeCases )
347{
348 EXPRESSION_EVALUATOR evaluator;
349
350 struct TestCase {
351 std::string expression;
352 std::string expected;
353 bool shouldError;
354 };
355
356 const std::vector<TestCase> cases = {
357 // Leap year boundaries
358 { "@{dateformat(365 + 365 + 59)}", "1972-02-29", false }, // Feb 29, 1972 (leap year)
359 { "@{dateformat(365 + 365 + 60)}", "1972-03-01", false }, // Mar 1, 1972
360
361 // Year boundaries
362 { "@{dateformat(365 - 1)}", "1970-12-31", false }, // Last day of 1970
363 { "@{dateformat(365)}", "1971-01-01", false }, // First day of 1971
364
365 // Month boundaries
366 { "@{dateformat(30)}", "1970-01-31", false }, // Last day of January
367 { "@{dateformat(31)}", "1970-02-01", false }, // First day of February
368 { "@{dateformat(58)}", "1970-02-28", false }, // Last day of February 1970 (not leap)
369 { "@{dateformat(59)}", "1970-03-01", false }, // First day of March 1970
370
371 // Large date values
372 { "@{dateformat(36525)}", "2070-01-01", false }, // 100 years after epoch
373
374 // Negative dates (before epoch)
375 { "@{dateformat(-365)}", "1969-01-01", false }, // One year before epoch
376 { "@{dateformat(-1)}", "1969-12-31", false }, // One day before epoch
377
378 // Weekday wrap-around
379 { "@{weekdayname(-1)}", "Wednesday", false }, // Day before Thursday
380 { "@{weekdayname(-7)}", "Thursday", false }, // One week before
381
382 // Edge case: very large weekday values
383 { "@{weekdayname(7000)}", "Thursday", false }, // Should still work
384 };
385
386 for( const auto& testCase : cases )
387 {
388 auto result = evaluator.Evaluate( wxString::FromUTF8( testCase.expression ) );
389
390 if( testCase.shouldError )
391 {
392 BOOST_CHECK( evaluator.HasErrors() );
393 }
394 else
395 {
396 BOOST_CHECK_MESSAGE( !evaluator.HasErrors(),
397 "Error in expression: " + testCase.expression );
398 BOOST_CHECK_EQUAL( result.ToStdString( wxConvUTF8 ), testCase.expected );
399 }
400 }
401}
402
406BOOST_AUTO_TEST_CASE( DateFormattingMixed )
407{
408 EXPRESSION_EVALUATOR evaluator;
409 evaluator.SetVariable( "days_offset", 100.0 );
410
411 struct TestCase {
412 std::string expression;
413 bool shouldWork;
414 };
415
416 const std::vector<TestCase> cases = {
417 // Complex expressions combining dates and variables
418 { "Today is @{dateformat(today())} which is @{weekdayname(today())}", true },
419 { "Date: @{dateformat(0 + ${days_offset}, \"long\")}", true },
420 { "In @{format(${days_offset})} days: @{dateformat(today() + ${days_offset})}", true },
421
422 // Nested function calls
423 { "@{upper(weekdayname(today()))}", true },
424 { "@{lower(dateformat(today(), \"long\"))}", true },
425
426 // Multiple date calculations
427 { "Start: @{dateformat(0)} End: @{dateformat(365)} Duration: @{365 - 0} days", true },
428 };
429
430 for( const auto& testCase : cases )
431 {
432 auto result = evaluator.Evaluate( wxString::FromUTF8( testCase.expression ) );
433
434 if( testCase.shouldWork )
435 {
436 BOOST_CHECK_MESSAGE( !evaluator.HasErrors(),
437 "Error in expression: " + testCase.expression +
438 " Result: " + result.ToStdString( wxConvUTF8 ) );
439 BOOST_CHECK( !result.empty() );
440 }
441 else
442 {
443 BOOST_CHECK( evaluator.HasErrors() );
444 }
445 }
446}
447
451BOOST_AUTO_TEST_CASE( DatePerformance )
452{
453 EXPRESSION_EVALUATOR evaluator;
454
455 // Test that date operations are reasonably fast
456 auto start = std::chrono::high_resolution_clock::now();
457
458 // Perform many date operations
459 for( int i = 0; i < 1000; ++i )
460 {
461 auto result = evaluator.Evaluate( "@{dateformat(" + std::to_string(i) + ")}" );
462 BOOST_CHECK( !evaluator.HasErrors() );
463 }
464
465 auto end = std::chrono::high_resolution_clock::now();
466 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>( end - start );
467
468 // Should complete in reasonable time (less than 100 milliseconds for 1000 operations)
469 BOOST_CHECK_LT( duration.count(), 100 );
470}
471
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)
VECTOR2I end
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")
BOOST_AUTO_TEST_CASE(DateFormatting)