/* * Copyright (c) 2019 TAOS Data, Inc. * * This program is free software: you can use, redistribute, and/or modify * it under the terms of the GNU Affero General Public License, version 3 * or later ("AGPL"), as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ #include #include #include #include "ttime.h" /** * Test suite for natural time unit boundary alignment * * Tests the alignToNaturalBoundary() function for: * - Week unit alignment (Monday 00:00:00) * - Month unit alignment (1st of month 00:00:00) * - Year unit alignment (Jan 1st 00:00:00) * - Multi-period alignment (2w, 3n, 2y) * - Offset application */ class TimeNaturalUnitsTest : public ::testing::Test { protected: void SetUp() override { // Use Asia/Shanghai timezone for consistent testing tz = NULL; taosSetGlobalTimezone("Asia/Shanghai"); } /** * Helper function to convert human-readable datetime to timestamp * @param year Year (e.g., 2026) * @param month Month (1-12) * @param day Day (1-31) * @param hour Hour (0-23) * @param minute Minute (0-59) * @param second Second (0-59) * @param precision Time precision (TSDB_TIME_PRECISION_MILLI/MICRO/NANO) * @return Timestamp in specified precision */ int64_t makeTimestamp(int year, int month, int day, int hour, int minute, int second, int8_t precision = TSDB_TIME_PRECISION_MILLI) { struct tm tm = {0}; tm.tm_year = year - 1900; tm.tm_mon = month - 1; tm.tm_mday = day; tm.tm_hour = hour; tm.tm_min = minute; tm.tm_sec = second; tm.tm_isdst = -1; time_t t = taosMktime(&tm, tz); int64_t ts = (int64_t)t; switch (precision) { case TSDB_TIME_PRECISION_MILLI: return ts * 1000LL; case TSDB_TIME_PRECISION_MICRO: return ts * 1000000LL; case TSDB_TIME_PRECISION_NANO: return ts * 1000000000LL; default: return ts * 1000LL; } } timezone_t tz; }; /** * Test week unit alignment to Monday 00:00:00 */ TEST_F(TimeNaturalUnitsTest, WeekAlignmentBasic) { // Test timestamp: 2026-03-10 15:30:00 (Tuesday) // Expected: align to 2026-03-09 00:00:00 (Monday) int64_t ts = makeTimestamp(2026, 3, 10, 15, 30, 0); int64_t result = alignToNaturalBoundary(ts, 'w', 1, 0, TSDB_TIME_PRECISION_MILLI, tz); // Convert result to struct tm to verify time_t t = result / 1000; struct tm tm; taosLocalTime(&t, &tm, NULL, 0, NULL); std::cout << "Original timestamp: " << ts << ", Aligned timestamp: " << result << std::endl; // Should be Monday (tm_wday = 1) EXPECT_EQ(tm.tm_wday, 1); // Should be 00:00:00 EXPECT_EQ(tm.tm_hour, 0); EXPECT_EQ(tm.tm_min, 0); EXPECT_EQ(tm.tm_sec, 0); } /** * Test month unit alignment to 1st of month 00:00:00 */ TEST_F(TimeNaturalUnitsTest, MonthAlignmentBasic) { // Test timestamp: 2026-03-15 12:00:00 // Expected: align to 2026-03-01 00:00:00 int64_t ts = makeTimestamp(2026, 3, 15, 12, 0, 0); int64_t result = alignToNaturalBoundary(ts, 'n', 1, 0, TSDB_TIME_PRECISION_MILLI, tz); time_t t = result / 1000; struct tm tm; taosLocalTime(&t, &tm, NULL, 0, NULL); std::cout << "Original timestamp: " << ts << ", Aligned timestamp: " << result << std::endl; // Should be 1st of month EXPECT_EQ(tm.tm_mday, 1); // Should be 00:00:00 EXPECT_EQ(tm.tm_hour, 0); EXPECT_EQ(tm.tm_min, 0); EXPECT_EQ(tm.tm_sec, 0); } /** * Test year unit alignment to Jan 1st 00:00:00 */ TEST_F(TimeNaturalUnitsTest, YearAlignmentBasic) { // Test timestamp: 2026-06-15 12:00:00 // Expected: align to 2026-01-01 00:00:00 int64_t ts = makeTimestamp(2026, 6, 15, 12, 0, 0); int64_t result = alignToNaturalBoundary(ts, 'y', 1, 0, TSDB_TIME_PRECISION_MILLI, tz); time_t t = result / 1000; struct tm tm; taosLocalTime(&t, &tm, NULL, 0, NULL); std::cout << "Original timestamp: " << ts << ", Aligned timestamp: " << result << std::endl; // Should be Jan 1st EXPECT_EQ(tm.tm_mon, 0); // January = 0 EXPECT_EQ(tm.tm_mday, 1); // Should be 00:00:00 EXPECT_EQ(tm.tm_hour, 0); EXPECT_EQ(tm.tm_min, 0); EXPECT_EQ(tm.tm_sec, 0); } /** * Test week unit with offset (1 day = Tuesday) */ TEST_F(TimeNaturalUnitsTest, WeekAlignmentWithOffset) { // Test timestamp: 2026-03-10 15:30:00 (Tuesday) // With 1 day offset, should align to Tuesday 00:00:00 int64_t ts = makeTimestamp(2026, 3, 10, 15, 30, 0); int64_t offset = 24LL * 60LL * 60LL * 1000LL; // 1 day in milliseconds int64_t result = alignToNaturalBoundary(ts, 'w', 1, offset, TSDB_TIME_PRECISION_MILLI, tz); time_t t = result / 1000; struct tm tm; taosLocalTime(&t, &tm, NULL, 0, NULL); std::cout << "Original timestamp: " << ts << ", Aligned timestamp: " << result << std::endl; // Should be Tuesday (tm_wday = 2) EXPECT_EQ(tm.tm_wday, 2); // Should be 00:00:00 EXPECT_EQ(tm.tm_hour, 0); EXPECT_EQ(tm.tm_min, 0); EXPECT_EQ(tm.tm_sec, 0); } /** * Test multi-period week alignment (2 weeks) */ TEST_F(TimeNaturalUnitsTest, MultiPeriodWeekAlignment) { // Test that 2-week periods align consistently // Two timestamps within the same 2-week period should align to the same boundary int64_t ts1 = makeTimestamp(2026, 3, 10, 0, 0, 0); // 2026-03-10 (Tuesday, week 1) int64_t ts2 = makeTimestamp(2026, 3, 13, 0, 0, 0); // 2026-03-13 (Friday, week 1) int64_t result1 = alignToNaturalBoundary(ts1, 'w', 2, 0, TSDB_TIME_PRECISION_MILLI, tz); int64_t result2 = alignToNaturalBoundary(ts2, 'w', 2, 0, TSDB_TIME_PRECISION_MILLI, tz); std::cout << "Timestamp 1: " << ts1 << ", Aligned 1: " << result1 << std::endl; std::cout << "Timestamp 2: " << ts2 << ", Aligned 2: " << result2 << std::endl; // Both should align to the same 2-week boundary EXPECT_EQ(result1, result2); // Test that adjacent 2-week periods align to different boundaries int64_t ts3 = makeTimestamp(2026, 3, 17, 0, 0, 0); // 2026-03-17 (next week) int64_t result3 = alignToNaturalBoundary(ts3, 'w', 2, 0, TSDB_TIME_PRECISION_MILLI, tz); std::cout << "Timestamp 3: " << ts3 << ", Aligned 3: " << result3 << std::endl; EXPECT_NE(result1, result3); // Should be different 2-week boundary // Window duration should be 14 days int64_t duration = result3 - result1; EXPECT_EQ(duration, 14LL * 24LL * 60LL * 60LL * 1000LL); } /** * Test multi-period month alignment (3 months / quarterly) */ TEST_F(TimeNaturalUnitsTest, MultiPeriodMonthAlignment) { // Test that 3-month periods align consistently // Timestamps within the same quarter should align to the same boundary int64_t ts1 = makeTimestamp(2026, 2, 15, 0, 0, 0); // 2026-02-15 (Q1) int64_t ts2 = makeTimestamp(2026, 3, 20, 0, 0, 0); // 2026-03-20 (Q1) int64_t result1 = alignToNaturalBoundary(ts1, 'n', 3, 0, TSDB_TIME_PRECISION_MILLI, tz); int64_t result2 = alignToNaturalBoundary(ts2, 'n', 3, 0, TSDB_TIME_PRECISION_MILLI, tz); std::cout << "Timestamp 1: " << ts1 << ", Aligned 1: " << result1 << std::endl; std::cout << "Timestamp 2: " << ts2 << ", Aligned 2: " << result2 << std::endl; // Both should align to Q1 start (2026-01-01) EXPECT_EQ(result1, result2); // Verify it's January 1st time_t t = result1 / 1000; struct tm tm; taosLocalTime(&t, &tm, NULL, 0, NULL); EXPECT_EQ(tm.tm_mon, 0); // January EXPECT_EQ(tm.tm_mday, 1); EXPECT_EQ(tm.tm_hour, 0); // Test that next quarter aligns to different boundary int64_t ts3 = makeTimestamp(2026, 4, 15, 0, 0, 0); // 2026-04-15 (Q2) int64_t result3 = alignToNaturalBoundary(ts3, 'n', 3, 0, TSDB_TIME_PRECISION_MILLI, tz); std::cout << "Timestamp 3: " << ts3 << ", Aligned 3: " << result3 << std::endl; EXPECT_NE(result1, result3); // Verify Q2 starts at April 1st time_t t3 = result3 / 1000; struct tm tm3; taosLocalTime(&t3, &tm3, NULL, 0, NULL); EXPECT_EQ(tm3.tm_mon, 3); // April EXPECT_EQ(tm3.tm_mday, 1); } /** * Test multi-period year alignment (2 years) */ TEST_F(TimeNaturalUnitsTest, MultiPeriodYearAlignment) { // Test that 2-year periods align consistently // Timestamps within the same 2-year period should align to the same boundary int64_t ts1 = makeTimestamp(2026, 3, 15, 0, 0, 0); // 2026-03-15 int64_t ts2 = makeTimestamp(2027, 8, 20, 0, 0, 0); // 2027-08-20 int64_t result1 = alignToNaturalBoundary(ts1, 'y', 2, 0, TSDB_TIME_PRECISION_MILLI, tz); int64_t result2 = alignToNaturalBoundary(ts2, 'y', 2, 0, TSDB_TIME_PRECISION_MILLI, tz); std::cout << "Timestamp 1: " << ts1 << ", Aligned 1: " << result1 << std::endl; std::cout << "Timestamp 2: " << ts2 << ", Aligned 2: " << result2 << std::endl; // Both should align to the same 2-year boundary (2026-01-01) EXPECT_EQ(result1, result2); // Verify it's 2026-01-01 time_t t = result1 / 1000; struct tm tm; taosLocalTime(&t, &tm, NULL, 0, NULL); EXPECT_EQ(tm.tm_year, 126); // 2026 EXPECT_EQ(tm.tm_mon, 0); // January EXPECT_EQ(tm.tm_mday, 1); EXPECT_EQ(tm.tm_hour, 0); // Test that next 2-year period aligns to different boundary int64_t ts3 = makeTimestamp(2028, 6, 15, 0, 0, 0); // 2028-06-15 int64_t result3 = alignToNaturalBoundary(ts3, 'y', 2, 0, TSDB_TIME_PRECISION_MILLI, tz); std::cout << "Timestamp 3: " << ts3 << ", Aligned 3: " << result3 << std::endl; EXPECT_NE(result1, result3); // Verify next period starts at 2028-01-01 time_t t3 = result3 / 1000; struct tm tm3; taosLocalTime(&t3, &tm3, NULL, 0, NULL); EXPECT_EQ(tm3.tm_year, 128); // 2028 EXPECT_EQ(tm3.tm_mon, 0); // January EXPECT_EQ(tm3.tm_mday, 1); } /** * Test microsecond precision */ TEST_F(TimeNaturalUnitsTest, MicrosecondPrecision) { // Test with microsecond precision int64_t ts = makeTimestamp(2026, 3, 10, 15, 30, 0, TSDB_TIME_PRECISION_MICRO); int64_t result = alignToNaturalBoundary(ts, 'w', 1, 0, TSDB_TIME_PRECISION_MICRO, tz); // Result should be in microseconds EXPECT_GT(result, 1000000000000LL); // Should be > 1 trillion (microseconds) // Convert to seconds for verification time_t t = result / 1000000; struct tm tm; taosLocalTime(&t, &tm, NULL, 0, NULL); EXPECT_EQ(tm.tm_wday, 1); // Monday EXPECT_EQ(tm.tm_hour, 0); EXPECT_EQ(tm.tm_min, 0); EXPECT_EQ(tm.tm_sec, 0); } /** * Test nanosecond precision */ TEST_F(TimeNaturalUnitsTest, NanosecondPrecision) { // Test with nanosecond precision int64_t ts = makeTimestamp(2026, 3, 10, 15, 30, 0, TSDB_TIME_PRECISION_NANO); int64_t result = alignToNaturalBoundary(ts, 'n', 1, 0, TSDB_TIME_PRECISION_NANO, tz); // Result should be in nanoseconds EXPECT_GT(result, 1000000000000000LL); // Should be > 1 quadrillion (nanoseconds) // Convert to seconds for verification time_t t = result / 1000000000; struct tm tm; taosLocalTime(&t, &tm, NULL, 0, NULL); EXPECT_EQ(tm.tm_mday, 1); // 1st of month EXPECT_EQ(tm.tm_hour, 0); EXPECT_EQ(tm.tm_min, 0); EXPECT_EQ(tm.tm_sec, 0); } /** * Test that non-natural units return timestamp as-is */ TEST_F(TimeNaturalUnitsTest, NonNaturalUnitsPassthrough) { int64_t ts = makeTimestamp(2026, 3, 10, 15, 30, 0); // Test with 'd' (day) unit - should return as-is int64_t result = alignToNaturalBoundary(ts, 'd', 1, 0, TSDB_TIME_PRECISION_MILLI, tz); EXPECT_EQ(result, ts); // Test with 'h' (hour) unit - should return as-is result = alignToNaturalBoundary(ts, 'h', 1, 0, TSDB_TIME_PRECISION_MILLI, tz); EXPECT_EQ(result, ts); } /** * Test getDuration() function for week unit conversion * Verifies that 'w' unit is correctly converted to 7 days */ TEST_F(TimeNaturalUnitsTest, GetDurationWeekUnit) { int64_t result = 0; int32_t code; // Test 1 week = 7 days in milliseconds code = getDuration(1, 'w', &result, TSDB_TIME_PRECISION_MILLI); EXPECT_EQ(code, TSDB_CODE_SUCCESS); EXPECT_EQ(result, 7LL * 24LL * 60LL * 60LL * 1000LL); // 7 days in ms // Test 2 weeks = 14 days in milliseconds code = getDuration(2, 'w', &result, TSDB_TIME_PRECISION_MILLI); EXPECT_EQ(code, TSDB_CODE_SUCCESS); EXPECT_EQ(result, 14LL * 24LL * 60LL * 60LL * 1000LL); // 14 days in ms // Test with microsecond precision code = getDuration(1, 'w', &result, TSDB_TIME_PRECISION_MICRO); EXPECT_EQ(code, TSDB_CODE_SUCCESS); EXPECT_EQ(result, 7LL * 24LL * 60LL * 60LL * 1000000LL); // 7 days in us // Test with nanosecond precision code = getDuration(1, 'w', &result, TSDB_TIME_PRECISION_NANO); EXPECT_EQ(code, TSDB_CODE_SUCCESS); EXPECT_EQ(result, 7LL * 24LL * 60LL * 60LL * 1000000000LL); // 7 days in ns // Test overflow protection (very large value) code = getDuration(INT64_MAX / (7LL * 24LL * 60LL * 60LL * 1000LL) + 1, 'w', &result, TSDB_TIME_PRECISION_MILLI); EXPECT_EQ(code, TSDB_CODE_OUT_OF_RANGE); } /** * Performance test for alignToNaturalBoundary() function * Verify that the function executes in < 1ms on average (SC-006) */ TEST_F(TimeNaturalUnitsTest, PerformanceAlignToNaturalBoundary) { const int iterations = 10000; int64_t ts = makeTimestamp(2026, 3, 10, 15, 30, 0); // Test week unit performance auto start = std::chrono::high_resolution_clock::now(); for (int i = 0; i < iterations; i++) { alignToNaturalBoundary(ts + i * 1000, 'w', 1, 0, TSDB_TIME_PRECISION_MILLI, tz); } auto end = std::chrono::high_resolution_clock::now(); auto duration_week = std::chrono::duration_cast(end - start).count(); double avg_week = duration_week / (double)iterations; std::cout << "Week alignment: " << iterations << " iterations in " << duration_week << " us" << std::endl; std::cout << "Average time per call: " << avg_week << " us" << std::endl; EXPECT_LT(avg_week, 1000.0); // Should be < 1ms (1000 us) // Test month unit performance start = std::chrono::high_resolution_clock::now(); for (int i = 0; i < iterations; i++) { alignToNaturalBoundary(ts + i * 1000, 'n', 1, 0, TSDB_TIME_PRECISION_MILLI, tz); } end = std::chrono::high_resolution_clock::now(); auto duration_month = std::chrono::duration_cast(end - start).count(); double avg_month = duration_month / (double)iterations; std::cout << "Month alignment: " << iterations << " iterations in " << duration_month << " us" << std::endl; std::cout << "Average time per call: " << avg_month << " us" << std::endl; EXPECT_LT(avg_month, 1000.0); // Should be < 1ms (1000 us) // Test year unit performance start = std::chrono::high_resolution_clock::now(); for (int i = 0; i < iterations; i++) { alignToNaturalBoundary(ts + i * 1000, 'y', 1, 0, TSDB_TIME_PRECISION_MILLI, tz); } end = std::chrono::high_resolution_clock::now(); auto duration_year = std::chrono::duration_cast(end - start).count(); double avg_year = duration_year / (double)iterations; std::cout << "Year alignment: " << iterations << " iterations in " << duration_year << " us" << std::endl; std::cout << "Average time per call: " << avg_year << " us" << std::endl; EXPECT_LT(avg_year, 1000.0); // Should be < 1ms (1000 us) } /** * Performance test for getDuration() function * Verify that the function executes in < 1ms on average (SC-006) */ TEST_F(TimeNaturalUnitsTest, PerformanceGetDuration) { const int iterations = 10000; int64_t result = 0; // Test week unit performance auto start = std::chrono::high_resolution_clock::now(); for (int i = 0; i < iterations; i++) { getDuration(1 + (i % 100), 'w', &result, TSDB_TIME_PRECISION_MILLI); } auto end = std::chrono::high_resolution_clock::now(); auto duration_week = std::chrono::duration_cast(end - start).count(); double avg_week = duration_week / (double)iterations; std::cout << "getDuration (week): " << iterations << " iterations in " << duration_week << " us" << std::endl; std::cout << "Average time per call: " << avg_week << " us" << std::endl; EXPECT_LT(avg_week, 1000.0); // Should be < 1ms (1000 us) // Test day unit performance start = std::chrono::high_resolution_clock::now(); for (int i = 0; i < iterations; i++) { getDuration(1 + (i % 100), 'd', &result, TSDB_TIME_PRECISION_MILLI); } end = std::chrono::high_resolution_clock::now(); auto duration_day = std::chrono::duration_cast(end - start).count(); double avg_day = duration_day / (double)iterations; std::cout << "getDuration (day): " << iterations << " iterations in " << duration_day << " us" << std::endl; std::cout << "Average time per call: " << avg_day << " us" << std::endl; EXPECT_LT(avg_day, 1000.0); // Should be < 1ms (1000 us) // Test hour unit performance start = std::chrono::high_resolution_clock::now(); for (int i = 0; i < iterations; i++) { getDuration(1 + (i % 100), 'h', &result, TSDB_TIME_PRECISION_MILLI); } end = std::chrono::high_resolution_clock::now(); auto duration_hour = std::chrono::duration_cast(end - start).count(); double avg_hour = duration_hour / (double)iterations; std::cout << "getDuration (hour): " << iterations << " iterations in " << duration_hour << " us" << std::endl; std::cout << "Average time per call: " << avg_hour << " us" << std::endl; EXPECT_LT(avg_hour, 1000.0); // Should be < 1ms (1000 us) }