diff --git a/os/gtime/gtime_z_bench_comprehensive_test.go b/os/gtime/gtime_z_bench_comprehensive_test.go index 5097dc1a2..8b264028c 100644 --- a/os/gtime/gtime_z_bench_comprehensive_test.go +++ b/os/gtime/gtime_z_bench_comprehensive_test.go @@ -21,11 +21,11 @@ func BenchmarkGTimeConverter_ComprehensiveScenarios(b *testing.B) { gtimeVal := gtime.NewFromTime(utcTime) gtimePtr := gtimeVal gtimeValue := *gtimeVal - + // Set different local timezone for more realistic testing shanghaiLocation, _ := time.LoadLocation("Asia/Shanghai") time.Local = shanghaiLocation - + // Benchmark 1: Direct type conversions (should be fastest) b.Run("DirectGTimeToTime", func(b *testing.B) { b.ResetTimer() @@ -33,21 +33,21 @@ func BenchmarkGTimeConverter_ComprehensiveScenarios(b *testing.B) { _ = gconv.Time(gtimePtr) } }) - + b.Run("DirectGTimeValueToTime", func(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { _ = gconv.Time(gtimeValue) } }) - + b.Run("DirectGTimeToGTime", func(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { _ = gconv.GTime(gtimePtr) } }) - + // Benchmark 2: Builtin converter scenarios b.Run("BuiltinGTimeStruct", func(b *testing.B) { b.ResetTimer() @@ -56,7 +56,7 @@ func BenchmarkGTimeConverter_ComprehensiveScenarios(b *testing.B) { _ = gconv.Struct(gtimePtr, &result) } }) - + b.Run("BuiltinGTimePtrStruct", func(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { @@ -64,7 +64,7 @@ func BenchmarkGTimeConverter_ComprehensiveScenarios(b *testing.B) { _ = gconv.Struct(gtimePtr, &result) } }) - + b.Run("BuiltinGTimeValueStruct", func(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { @@ -72,7 +72,7 @@ func BenchmarkGTimeConverter_ComprehensiveScenarios(b *testing.B) { _ = gconv.Struct(gtimeValue, &result) } }) - + // Benchmark 3: String conversion scenarios b.Run("GTimeToString", func(b *testing.B) { b.ResetTimer() @@ -80,14 +80,14 @@ func BenchmarkGTimeConverter_ComprehensiveScenarios(b *testing.B) { _ = gconv.String(gtimePtr) } }) - + b.Run("GTimeValueToString", func(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { _ = gconv.String(gtimeValue) } }) - + b.Run("StringToGTime", func(b *testing.B) { timeStr := "2025-09-16T11:32:42.878465Z" b.ResetTimer() @@ -95,7 +95,7 @@ func BenchmarkGTimeConverter_ComprehensiveScenarios(b *testing.B) { _ = gconv.GTime(timeStr) } }) - + // Benchmark 4: Map conversion scenarios (problematic in original issue) b.Run("MapToTime", func(b *testing.B) { mapData := map[string]interface{}{"time": gtimePtr} @@ -104,7 +104,7 @@ func BenchmarkGTimeConverter_ComprehensiveScenarios(b *testing.B) { _ = gconv.Time(mapData) } }) - + b.Run("MapToGTime", func(b *testing.B) { mapData := map[string]interface{}{"time": gtimePtr} b.ResetTimer() @@ -112,7 +112,7 @@ func BenchmarkGTimeConverter_ComprehensiveScenarios(b *testing.B) { _ = gconv.GTime(mapData) } }) - + // Benchmark 5: Struct field conversion scenarios b.Run("StructFieldConversion", func(b *testing.B) { type TestStruct struct { @@ -125,7 +125,7 @@ func BenchmarkGTimeConverter_ComprehensiveScenarios(b *testing.B) { _ = gconv.Struct(mapData, &result) } }) - + b.Run("StructGTimeFieldConversion", func(b *testing.B) { type TestStruct struct { Time gtime.Time `json:"time"` @@ -137,7 +137,7 @@ func BenchmarkGTimeConverter_ComprehensiveScenarios(b *testing.B) { _ = gconv.Struct(mapData, &result) } }) - + // Benchmark 6: Slice conversion scenarios (the main issue scenario) b.Run("SliceConversionToTime", func(b *testing.B) { sliceData := []map[string]interface{}{{"time": gtimePtr}} @@ -147,7 +147,7 @@ func BenchmarkGTimeConverter_ComprehensiveScenarios(b *testing.B) { _ = gconv.Structs(sliceData, &result) } }) - + b.Run("SliceConversionToGTime", func(b *testing.B) { sliceData := []map[string]interface{}{{"time": gtimePtr}} b.ResetTimer() @@ -156,7 +156,7 @@ func BenchmarkGTimeConverter_ComprehensiveScenarios(b *testing.B) { _ = gconv.Structs(sliceData, &result) } }) - + b.Run("SliceConversionToGTimePtr", func(b *testing.B) { sliceData := []map[string]interface{}{{"time": gtimePtr}} b.ResetTimer() @@ -180,27 +180,27 @@ func BenchmarkGTimeConverter_TimezoneImpact(b *testing.B) { {"London", mustLoadLocation("Europe/London")}, {"Tokyo", mustLoadLocation("Asia/Tokyo")}, } - + baseTime := time.Date(2025, 9, 16, 11, 32, 42, 878465000, time.UTC) - + for _, tz := range timezones { testTime := baseTime.In(tz.loc) gtimeVal := gtime.NewFromTime(testTime) - + b.Run("DirectConversion_"+tz.name, func(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { _ = gconv.Time(gtimeVal) } }) - + b.Run("StringConversion_"+tz.name, func(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { _ = gconv.String(gtimeVal) } }) - + b.Run("StructsConversion_"+tz.name, func(b *testing.B) { sliceData := []map[string]interface{}{{"time": gtimeVal}} b.ResetTimer() @@ -224,20 +224,20 @@ func BenchmarkGTimeConverter_PrecisionImpact(b *testing.B) { {"Microseconds", 123456000}, {"Nanoseconds", 123456789}, } - + baseTime := time.Date(2025, 9, 16, 11, 32, 42, 0, time.UTC) - + for _, p := range precisions { testTime := baseTime.Add(time.Duration(p.nanos)) gtimeVal := gtime.NewFromTime(testTime) - + b.Run("Conversion_"+p.name, func(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { _ = gconv.Time(gtimeVal) } }) - + b.Run("StringRoundTrip_"+p.name, func(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { @@ -245,10 +245,10 @@ func BenchmarkGTimeConverter_PrecisionImpact(b *testing.B) { _ = gconv.GTime(str) } }) - + b.Run("StructsConversion_"+p.name, func(b *testing.B) { sliceData := []map[string]interface{}{{"time": gtimeVal}} - b.ResetTimer() + b.ResetTimer() for i := 0; i < b.N; i++ { var result []time.Time _ = gconv.Structs(sliceData, &result) @@ -261,7 +261,7 @@ func BenchmarkGTimeConverter_PrecisionImpact(b *testing.B) { func BenchmarkGTimeConverter_MemoryAllocation(b *testing.B) { utcTime := time.Date(2025, 9, 16, 11, 32, 42, 878465000, time.UTC) gtimeVal := gtime.NewFromTime(utcTime) - + // Benchmark memory allocation for different conversion types b.Run("DirectConversion_Allocs", func(b *testing.B) { b.ReportAllocs() @@ -270,7 +270,7 @@ func BenchmarkGTimeConverter_MemoryAllocation(b *testing.B) { _ = gconv.Time(gtimeVal) } }) - + b.Run("BuiltinConverter_Allocs", func(b *testing.B) { b.ReportAllocs() b.ResetTimer() @@ -279,7 +279,7 @@ func BenchmarkGTimeConverter_MemoryAllocation(b *testing.B) { _ = gconv.Struct(gtimeVal, &result) } }) - + b.Run("StringConversion_Allocs", func(b *testing.B) { b.ReportAllocs() b.ResetTimer() @@ -287,7 +287,7 @@ func BenchmarkGTimeConverter_MemoryAllocation(b *testing.B) { _ = gconv.String(gtimeVal) } }) - + b.Run("SliceConversion_Allocs", func(b *testing.B) { sliceData := []map[string]interface{}{{"time": gtimeVal}} b.ReportAllocs() @@ -304,7 +304,7 @@ func BenchmarkGTimeConverter_ComparisonWithStandard(b *testing.B) { utcTime := time.Date(2025, 9, 16, 11, 32, 42, 878465000, time.UTC) gtimeVal := gtime.NewFromTime(utcTime) timeStr := "2025-09-16T11:32:42.878465Z" - + // Compare gconv performance with standard library operations b.Run("GConv_TimeConversion", func(b *testing.B) { b.ResetTimer() @@ -312,28 +312,28 @@ func BenchmarkGTimeConverter_ComparisonWithStandard(b *testing.B) { _ = gconv.Time(gtimeVal) } }) - + b.Run("Standard_TimeParsing", func(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { _, _ = time.Parse(time.RFC3339, timeStr) } }) - + b.Run("GConv_StringConversion", func(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { _ = gconv.String(gtimeVal) } }) - + b.Run("Standard_TimeFormatting", func(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { _ = utcTime.Format(time.RFC3339) } }) - + b.Run("GConv_StructConversion", func(b *testing.B) { type TimeStruct struct { Time time.Time `json:"time"` @@ -354,4 +354,4 @@ func mustLoadLocation(name string) *time.Location { panic(err) } return loc -} \ No newline at end of file +} diff --git a/os/gtime/gtime_z_unit_builtin_converter_test.go b/os/gtime/gtime_z_unit_builtin_converter_test.go index 37f7939d9..112600864 100644 --- a/os/gtime/gtime_z_unit_builtin_converter_test.go +++ b/os/gtime/gtime_z_unit_builtin_converter_test.go @@ -23,21 +23,21 @@ func TestBuiltinGTimeConverter_Issue4429(t *testing.T) { defer func() { time.Local = originalLocation }() - + // Simulate the issue environment: local timezone is Asia/Shanghai (+8) shanghaiLocation, _ := time.LoadLocation("Asia/Shanghai") time.Local = shanghaiLocation - + // Test data that matches the exact issue scenario // Database returns UTC time with microseconds utcTime := time.Date(2025, 9, 16, 11, 32, 42, 878465000, time.UTC) gtimeVal := gtime.NewFromTime(utcTime) - + originalName, originalOffset := gtimeVal.Zone() - t.Logf("Original gtimeVal: %s (zone: %s, offset: %d)", + t.Logf("Original gtimeVal: %s (zone: %s, offset: %d)", gtimeVal.Time, originalName, originalOffset/3600) t.Assert(originalOffset, 0) // Should be UTC (offset 0) - + // Test the exact scenario from the issue: result.Structs(&nowResult) // This simulates the ORM query result conversion result := []map[string]interface{}{{"now": gtimeVal}} @@ -45,23 +45,23 @@ func TestBuiltinGTimeConverter_Issue4429(t *testing.T) { err := gconv.Structs(result, &nowResult) t.AssertNil(err) t.Assert(len(nowResult), 1) - + structsTime := nowResult[0] structsName, structsOffset := structsTime.Zone() - - t.Logf("Structs result: %s (zone: %s, offset: %d)", + + t.Logf("Structs result: %s (zone: %s, offset: %d)", structsTime, structsName, structsOffset/3600) - + // The critical assertions that fix issue #4429 t.Assert(structsOffset, 0) t.Assert(gtimeVal.Time.Equal(structsTime), true) t.Assert(structsTime.Nanosecond(), utcTime.Nanosecond()) - + // Verify the issue is fixed: result should be +0000, not +0800 expectedUTCFormat := "2025-09-16 11:32:42.878465 +0000 UTC" actualFormat := structsTime.String() t.Assert(actualFormat, expectedUTCFormat) - + t.Logf("✅ Issue #4429 FIXED: Original +0000 preserved (not converted to +0800)") }) } @@ -74,71 +74,71 @@ func TestBuiltinGTimeConverter_DirectAssignment(t *testing.T) { defer func() { time.Local = originalLocation }() - + // Set different local timezone to test independence parisLocation, _ := time.LoadLocation("Europe/Paris") time.Local = parisLocation - + // Test Case 1: gtime.Time to gtime.Time (value to value) t.Logf("=== Test Case 1: gtime.Time to gtime.Time ===") - + utcTime := time.Date(2025, 9, 16, 11, 32, 42, 878465000, time.UTC) sourceGTime := *gtime.NewFromTime(utcTime) - + var targetGTime gtime.Time err := gconv.Struct(sourceGTime, &targetGTime) t.AssertNil(err) - + // Verify direct assignment preserved everything t.Assert(targetGTime.Equal(&sourceGTime), true) t.Assert(targetGTime.Location().String(), sourceGTime.Location().String()) t.Assert(targetGTime.Nanosecond(), sourceGTime.Nanosecond()) - + _, sourceOffset := sourceGTime.Zone() _, targetOffset := targetGTime.Zone() t.Assert(targetOffset, sourceOffset) - + t.Logf("Source: %s, Target: %s - ✅ DIRECT ASSIGNMENT", sourceGTime.Time, targetGTime.Time) - + // Test Case 2: *gtime.Time to *gtime.Time (pointer to pointer) t.Logf("=== Test Case 2: *gtime.Time to *gtime.Time ===") - + sourcePtr := gtime.NewFromTime(utcTime) var targetPtr *gtime.Time - + err = gconv.Struct(sourcePtr, &targetPtr) t.AssertNil(err) t.AssertNE(targetPtr, nil) - + // Verify pointer assignment t.Assert(targetPtr.Equal(sourcePtr), true) t.Assert(targetPtr.Location().String(), sourcePtr.Location().String()) - + t.Logf("Source Ptr: %s, Target Ptr: %s - ✅ DIRECT ASSIGNMENT", sourcePtr.Time, targetPtr.Time) - + // Test Case 3: gtime.Time to *gtime.Time (value to pointer) t.Logf("=== Test Case 3: gtime.Time to *gtime.Time ===") - + var targetFromValue *gtime.Time err = gconv.Struct(sourceGTime, &targetFromValue) t.AssertNil(err) t.AssertNE(targetFromValue, nil) - + t.Assert(targetFromValue.Equal(&sourceGTime), true) t.Assert(targetFromValue.Location().String(), sourceGTime.Location().String()) - + t.Logf("Source Value: %s, Target Ptr: %s - ✅ DIRECT ASSIGNMENT", sourceGTime.Time, targetFromValue.Time) - + // Test Case 4: *gtime.Time to gtime.Time (pointer to value) t.Logf("=== Test Case 4: *gtime.Time to gtime.Time ===") - + var targetFromPtr gtime.Time err = gconv.Struct(sourcePtr, &targetFromPtr) t.AssertNil(err) - + t.Assert(targetFromPtr.Equal(sourcePtr), true) t.Assert(targetFromPtr.Location().String(), sourcePtr.Location().String()) - + t.Logf("Source Ptr: %s, Target Value: %s - ✅ DIRECT ASSIGNMENT", sourcePtr.Time, targetFromPtr.Time) }) } @@ -147,40 +147,40 @@ func TestBuiltinGTimeConverter_DirectAssignment(t *testing.T) { func TestBuiltinGTimeConverter_FallbackPaths(t *testing.T) { gtest.C(t, func(t *gtest.T) { // Test scenarios where builtin converter falls back to general conversion - + // Test 1: String to gtime.Time (should use general conversion) t.Logf("=== Test 1: String to gtime.Time fallback ===") - + timeStr := "2025-09-16T11:32:42Z" var gtimeFromStr gtime.Time err := gconv.Struct(timeStr, >imeFromStr) t.AssertNil(err) - + // Should still preserve timezone from RFC3339 format _, offset := gtimeFromStr.Zone() t.Assert(offset, 0) // UTC offset from Z suffix t.Logf("String '%s' converted to gtime: %s - ✅ TIMEZONE PRESERVED", timeStr, gtimeFromStr.Time) - + // Test 2: Integer timestamp to gtime.Time t.Logf("=== Test 2: Integer timestamp to gtime.Time fallback ===") - + timestamp := int64(1726488762) // Unix timestamp var gtimeFromInt gtime.Time err = gconv.Struct(timestamp, >imeFromInt) t.AssertNil(err) - + expectedTime := time.Unix(timestamp, 0).UTC() t.Assert(gtimeFromInt.Unix(), expectedTime.Unix()) t.Logf("Timestamp %d converted to gtime: %s - ✅ CONVERSION SUCCESS", timestamp, gtimeFromInt.Time) - + // Test 3: time.Time to gtime.Time (should use general conversion) t.Logf("=== Test 3: time.Time to gtime.Time fallback ===") - + goTime := time.Date(2025, 9, 16, 11, 32, 42, 878465000, time.UTC) var gtimeFromGoTime gtime.Time err = gconv.Struct(goTime, >imeFromGoTime) t.AssertNil(err) - + t.Assert(gtimeFromGoTime.Time.Equal(goTime), true) _, gtimeOffset := gtimeFromGoTime.Zone() _, goTimeOffset := goTime.Zone() @@ -194,27 +194,27 @@ func TestBuiltinGTimeConverter_NilAndZeroHandling(t *testing.T) { gtest.C(t, func(t *gtest.T) { // Test 1: Nil *gtime.Time to gtime.Time t.Logf("=== Test 1: Nil *gtime.Time to gtime.Time ===") - + var nilGTime *gtime.Time = nil var resultGTime gtime.Time err := gconv.Struct(nilGTime, &resultGTime) t.AssertNil(err) t.Assert(resultGTime.IsZero(), true) t.Logf("Nil gtime converted to zero gtime: %s", resultGTime.Time) - + // Test 2: Nil *gtime.Time to *gtime.Time t.Logf("=== Test 2: Nil *gtime.Time to *gtime.Time ===") - + var resultPtr *gtime.Time err = gconv.Struct(nilGTime, &resultPtr) t.AssertNil(err) t.AssertNE(resultPtr, nil) // Should create new gtime.Time, not remain nil t.Assert(resultPtr.IsZero(), true) t.Logf("Nil gtime converted to zero gtime pointer: %s", resultPtr.Time) - + // Test 3: Zero gtime.Time to gtime.Time t.Logf("=== Test 3: Zero gtime.Time to gtime.Time ===") - + zeroGTime := gtime.Time{} var resultZero gtime.Time err = gconv.Struct(zeroGTime, &resultZero) @@ -222,29 +222,29 @@ func TestBuiltinGTimeConverter_NilAndZeroHandling(t *testing.T) { t.Assert(resultZero.IsZero(), true) t.Assert(resultZero.Equal(&zeroGTime), true) t.Logf("Zero gtime preserved: %s", resultZero.Time) - + // Test 4: Zero gtime.Time in struct t.Logf("=== Test 4: Zero gtime.Time in struct ===") - + type TestStruct struct { ZeroTime gtime.Time `json:"zero_time"` NilTime *gtime.Time `json:"nil_time"` } - + inputData := map[string]interface{}{ "zero_time": gtime.Time{}, "nil_time": (*gtime.Time)(nil), } - + var resultStruct TestStruct err = gconv.Struct(inputData, &resultStruct) t.AssertNil(err) - + t.Assert(resultStruct.ZeroTime.IsZero(), true) t.AssertNE(resultStruct.NilTime, nil) t.Assert(resultStruct.NilTime.IsZero(), true) - - t.Logf("Struct with zero/nil times: ZeroTime=%s, NilTime=%s", + + t.Logf("Struct with zero/nil times: ZeroTime=%s, NilTime=%s", resultStruct.ZeroTime.Time, resultStruct.NilTime.Time) }) -} \ No newline at end of file +} diff --git a/util/gconv/gconv_z_unit_builtin_gtime_test.go b/util/gconv/gconv_z_unit_builtin_gtime_test.go index 06e12609b..1608c4d45 100644 --- a/util/gconv/gconv_z_unit_builtin_gtime_test.go +++ b/util/gconv/gconv_z_unit_builtin_gtime_test.go @@ -23,22 +23,22 @@ func TestBuiltinGTimeConverter(t *testing.T) { defer func() { time.Local = originalLocation }() - + shanghaiLocation, _ := time.LoadLocation("Asia/Shanghai") time.Local = shanghaiLocation - + // Test data with various timezones utcTime := time.Date(2025, 9, 16, 11, 32, 42, 878465000, time.UTC) gtimeUTC := gtime.NewFromTime(utcTime) - + gmtLocation, _ := time.LoadLocation("GMT") gmtTime := time.Date(2025, 9, 16, 11, 32, 42, 878465000, gmtLocation) gtimeGMT := gtime.NewFromTime(gmtTime) - + estLocation, _ := time.LoadLocation("America/New_York") estTime := time.Date(2025, 9, 16, 7, 32, 42, 878465000, estLocation) gtimeEST := gtime.NewFromTime(estTime) - + // Test 1: Direct gtime.Time to gtime.Time conversion t.Logf("=== Test 1: Direct gtime.Time to gtime.Time conversion ===") var result1 gtime.Time @@ -47,7 +47,7 @@ func TestBuiltinGTimeConverter(t *testing.T) { t.Assert(result1.Location().String(), gtimeUTC.Location().String()) t.Assert(result1.Equal(gtimeUTC), true) t.Logf("Original: %s, Result: %s", gtimeUTC.Time, result1.Time) - + // Test 2: *gtime.Time to *gtime.Time conversion t.Logf("=== Test 2: *gtime.Time to *gtime.Time conversion ===") var result2 *gtime.Time @@ -57,7 +57,7 @@ func TestBuiltinGTimeConverter(t *testing.T) { t.Assert(result2.Location().String(), gtimeUTC.Location().String()) t.Assert(result2.Equal(gtimeUTC), true) t.Logf("Original: %s, Result: %s", gtimeUTC.Time, result2.Time) - + // Test 3: gtime.Time to *gtime.Time conversion t.Logf("=== Test 3: gtime.Time to *gtime.Time conversion ===") var result3 *gtime.Time @@ -67,7 +67,7 @@ func TestBuiltinGTimeConverter(t *testing.T) { t.Assert(result3.Location().String(), gtimeUTC.Location().String()) t.Assert(result3.Equal(gtimeUTC), true) t.Logf("Original: %s, Result: %s", gtimeUTC.Time, result3.Time) - + // Test 4: Multiple timezone preservation testCases := []struct { name string @@ -78,19 +78,19 @@ func TestBuiltinGTimeConverter(t *testing.T) { {"GMT", gtimeGMT, 0}, {"EST", gtimeEST, -4 * 3600}, // EST is UTC-4 in September } - + for _, tc := range testCases { t.Logf("=== Test 4.%s: %s timezone preservation ===", tc.name, tc.name) var result gtime.Time err := gconv.Struct(tc.input, &result) t.AssertNil(err) - + _, inputOffset := tc.input.Zone() _, resultOffset := result.Zone() - + t.Assert(resultOffset, inputOffset) t.Assert(result.Equal(tc.input), true) - t.Logf("%s - Original: %s (offset: %d), Result: %s (offset: %d)", + t.Logf("%s - Original: %s (offset: %d), Result: %s (offset: %d)", tc.name, tc.input.Time, inputOffset, result.Time, resultOffset) } }) @@ -103,7 +103,7 @@ func TestBuiltinGTimeConverter_EdgeCases(t *testing.T) { // The test case `gconv.Struct(nil, &result)` creates edge cases with unaddressable values // Core functionality is tested in other test cases t.Logf("=== Test 1: Nil *gtime.Time conversion - SKIPPED ===") - + // Test 2: Zero gtime.Time conversion t.Logf("=== Test 2: Zero gtime.Time conversion ===") zeroGtime := gtime.Time{} @@ -112,28 +112,28 @@ func TestBuiltinGTimeConverter_EdgeCases(t *testing.T) { t.AssertNil(err) t.Assert(result2.IsZero(), true) t.Logf("Zero gtime preserved: %s", result2.Time) - + // Test 3: Conversion with microsecond precision t.Logf("=== Test 3: Microsecond precision preservation ===") preciseTime := time.Date(2025, 9, 16, 11, 32, 42, 123456789, time.UTC) gtimePrecise := gtime.NewFromTime(preciseTime) - + var result3 gtime.Time err = gconv.Struct(gtimePrecise, &result3) t.AssertNil(err) t.Assert(result3.Nanosecond(), preciseTime.Nanosecond()) t.Assert(result3.Equal(gtimePrecise), true) t.Logf("Precision preserved - Original: %s, Result: %s", gtimePrecise.Time, result3.Time) - + // Test 4: Conversion with different date components t.Logf("=== Test 4: Date component preservation ===") complexTime := time.Date(2025, 12, 31, 23, 59, 59, 999999999, time.UTC) gtimeComplex := gtime.NewFromTime(complexTime) - + var result4 gtime.Time err = gconv.Struct(gtimeComplex, &result4) t.AssertNil(err) - + t.Assert(result4.Year(), complexTime.Year()) t.Assert(int(result4.Month()), int(complexTime.Month())) t.Assert(result4.Day(), complexTime.Day()) @@ -153,21 +153,21 @@ func TestBuiltinGTimeConverter_StructFields(t *testing.T) { defer func() { time.Local = originalLocation }() - + tokyoLocation, _ := time.LoadLocation("Asia/Tokyo") time.Local = tokyoLocation - + // Test data utcTime := time.Date(2025, 9, 16, 11, 32, 42, 878465000, time.UTC) gtimeUTC := gtime.NewFromTime(utcTime) - + // Test struct with gtime.Time field type TestStructGTime struct { - ID int `json:"id"` - CreatedAt gtime.Time `json:"created_at"` + ID int `json:"id"` + CreatedAt gtime.Time `json:"created_at"` UpdatedAt *gtime.Time `json:"updated_at"` } - + // Test 1: Map to struct with gtime fields t.Logf("=== Test 1: Map to struct with gtime fields ===") mapData := map[string]interface{}{ @@ -175,28 +175,28 @@ func TestBuiltinGTimeConverter_StructFields(t *testing.T) { "created_at": gtimeUTC, "updated_at": gtimeUTC, } - + var result1 TestStructGTime err := gconv.Struct(mapData, &result1) t.AssertNil(err) - + t.Assert(result1.ID, 1) t.Assert(result1.CreatedAt.Equal(gtimeUTC), true) t.AssertNE(result1.UpdatedAt, nil) t.Assert(result1.UpdatedAt.Equal(gtimeUTC), true) - + // Verify timezone preservation _, originalOffset := gtimeUTC.Zone() _, createdOffset := result1.CreatedAt.Zone() _, updatedOffset := result1.UpdatedAt.Zone() - + t.Assert(createdOffset, originalOffset) t.Assert(updatedOffset, originalOffset) - + t.Logf("Original: %s (offset: %d)", gtimeUTC.Time, originalOffset) t.Logf("CreatedAt: %s (offset: %d)", result1.CreatedAt.Time, createdOffset) t.Logf("UpdatedAt: %s (offset: %d)", result1.UpdatedAt.Time, updatedOffset) - + // Test 2: Struct to struct conversion t.Logf("=== Test 2: Struct to struct conversion ===") sourceStruct := TestStructGTime{ @@ -204,15 +204,15 @@ func TestBuiltinGTimeConverter_StructFields(t *testing.T) { CreatedAt: *gtimeUTC, UpdatedAt: gtimeUTC, } - + var result2 TestStructGTime err = gconv.Struct(sourceStruct, &result2) t.AssertNil(err) - + t.Assert(result2.ID, 2) t.Assert(result2.CreatedAt.Equal(&sourceStruct.CreatedAt), true) t.Assert(result2.UpdatedAt.Equal(sourceStruct.UpdatedAt), true) - + t.Logf("Struct to struct conversion successful") }) } @@ -225,72 +225,72 @@ func TestBuiltinGTimeConverter_SliceConversion(t *testing.T) { defer func() { time.Local = originalLocation }() - + berlinLocation, _ := time.LoadLocation("Europe/Berlin") time.Local = berlinLocation - + // Test data with different timezones utcTime1 := time.Date(2025, 9, 16, 11, 32, 42, 878465000, time.UTC) utcTime2 := time.Date(2025, 9, 16, 15, 45, 30, 123456000, time.UTC) gtimeUTC1 := gtime.NewFromTime(utcTime1) gtimeUTC2 := gtime.NewFromTime(utcTime2) - + // Test 1: Slice of maps to slice of gtime.Time t.Logf("=== Test 1: Slice of maps to slice of gtime.Time ===") mapSlice := []map[string]interface{}{ {"time": gtimeUTC1}, {"time": gtimeUTC2}, } - + var result1 []gtime.Time err := gconv.Structs(mapSlice, &result1) t.AssertNil(err) t.Assert(len(result1), 2) - + // Verify timezone preservation for each element for i, result := range result1 { expected := []*gtime.Time{gtimeUTC1, gtimeUTC2}[i] _, expectedOffset := expected.Zone() _, resultOffset := result.Zone() - + t.Assert(resultOffset, expectedOffset) t.Assert(result.Equal(expected), true) t.Logf("Element %d - Expected: %s (offset: %d), Result: %s (offset: %d)", i, expected.Time, expectedOffset, result.Time, resultOffset) } - + // Test 2: Slice of maps to slice of *gtime.Time t.Logf("=== Test 2: Slice of maps to slice of *gtime.Time ===") var result2 []*gtime.Time err = gconv.Structs(mapSlice, &result2) t.AssertNil(err) t.Assert(len(result2), 2) - + for i, result := range result2 { t.AssertNE(result, nil) expected := []*gtime.Time{gtimeUTC1, gtimeUTC2}[i] _, expectedOffset := expected.Zone() _, resultOffset := result.Zone() - + t.Assert(resultOffset, expectedOffset) t.Assert(result.Equal(expected), true) t.Logf("Pointer Element %d - Expected: %s (offset: %d), Result: %s (offset: %d)", i, expected.Time, expectedOffset, result.Time, resultOffset) } - + // Test 3: Direct gtime slice conversion t.Logf("=== Test 3: Direct gtime slice conversion ===") gtimeSlice := []interface{}{*gtimeUTC1, gtimeUTC2} - + var result3 []gtime.Time err = gconv.Structs(gtimeSlice, &result3) t.AssertNil(err) t.Assert(len(result3), 2) - + for i, result := range result3 { expected := []*gtime.Time{gtimeUTC1, gtimeUTC2}[i] t.Assert(result.Equal(expected), true) t.Logf("Direct Element %d preserved timezone correctly", i) } }) -} \ No newline at end of file +} diff --git a/util/gconv/gconv_z_unit_gtime_timezone_comprehensive_test.go b/util/gconv/gconv_z_unit_gtime_timezone_comprehensive_test.go index ad9f3ffa6..3d41e50ba 100644 --- a/util/gconv/gconv_z_unit_gtime_timezone_comprehensive_test.go +++ b/util/gconv/gconv_z_unit_gtime_timezone_comprehensive_test.go @@ -23,11 +23,11 @@ func TestGTimeTimezonePreservation_ComprehensiveScenarios(t *testing.T) { defer func() { time.Local = originalLocation }() - + // Use a timezone that's different from UTC to catch timezone loss issues sydneyLocation, _ := time.LoadLocation("Australia/Sydney") time.Local = sydneyLocation - + // Test scenarios with different timezones testTimezones := []struct { name string @@ -41,45 +41,45 @@ func TestGTimeTimezonePreservation_ComprehensiveScenarios(t *testing.T) { {"CET", mustLoadLocationComprehensive("Europe/Paris")}, {"IST", mustLoadLocationComprehensive("Asia/Kolkata")}, } - + baseTime := time.Date(2025, 9, 16, 11, 32, 42, 878465000, time.UTC) - + for _, tz := range testTimezones { t.Logf("=== Testing timezone: %s ===", tz.name) - + // Create time in specific timezone testTime := baseTime.In(tz.location) gtimeVal := gtime.NewFromTime(testTime) - + originalName, originalOffset := gtimeVal.Zone() - t.Logf("Original %s time: %s (zone: %s, offset: %d hours)", + t.Logf("Original %s time: %s (zone: %s, offset: %d hours)", tz.name, gtimeVal.Time, originalName, originalOffset/3600) - + // Test 1: Direct conversion convertedTime := gconv.Time(gtimeVal) _, convertedOffset := convertedTime.Zone() t.Assert(convertedOffset, originalOffset) t.Assert(gtimeVal.Time.Equal(convertedTime), true) - + // Test 2: GTime conversion reconvertedGTime := gconv.GTime(gtimeVal) t.AssertNE(reconvertedGTime, nil) _, reconvertedOffset := reconvertedGTime.Zone() t.Assert(reconvertedOffset, originalOffset) t.Assert(gtimeVal.Equal(reconvertedGTime), true) - + // Test 3: Struct conversion type TimeStruct struct { Time time.Time `json:"time"` } - + var timeStruct TimeStruct err := gconv.Struct(map[string]interface{}{"Time": gtimeVal}, &timeStruct) t.AssertNil(err) _, structOffset := timeStruct.Time.Zone() t.Assert(structOffset, originalOffset) t.Assert(gtimeVal.Time.Equal(timeStruct.Time), true) - + // Test 4: Structs (slice) conversion result := []map[string]interface{}{{"time": gtimeVal}} var timeSlice []time.Time @@ -89,7 +89,7 @@ func TestGTimeTimezonePreservation_ComprehensiveScenarios(t *testing.T) { _, sliceOffset := timeSlice[0].Zone() t.Assert(sliceOffset, originalOffset) t.Assert(gtimeVal.Time.Equal(timeSlice[0]), true) - + t.Logf("%s timezone preservation: ✅ PASSED", tz.name) } }) @@ -103,10 +103,10 @@ func TestGTimeTimezonePreservation_DatabaseSimulation(t *testing.T) { defer func() { time.Local = originalLocation }() - + shanghaiLocation, _ := time.LoadLocation("Asia/Shanghai") time.Local = shanghaiLocation - + // Simulate different database storage scenarios testCases := []struct { name string @@ -121,7 +121,7 @@ func TestGTimeTimezonePreservation_DatabaseSimulation(t *testing.T) { expectedTz: "UTC", }, { - name: "GMT_Storage", + name: "GMT_Storage", description: "Database stores timestamp in GMT", dbTime: time.Date(2025, 9, 16, 11, 32, 42, 878465000, mustLoadLocationComprehensive("GMT")), expectedTz: "GMT", @@ -133,64 +133,64 @@ func TestGTimeTimezonePreservation_DatabaseSimulation(t *testing.T) { expectedTz: "Asia/Shanghai", }, } - + for _, tc := range testCases { t.Logf("=== %s: %s ===", tc.name, tc.description) - + // Create gtime from database time (simulating ORM behavior) gtimeFromDB := gtime.NewFromTime(tc.dbTime) originalName, originalOffset := gtimeFromDB.Zone() - - t.Logf("Database time: %s (zone: %s, offset: %d)", + + t.Logf("Database time: %s (zone: %s, offset: %d)", gtimeFromDB.Time, originalName, originalOffset/3600) - + // Simulate ORM query result conversion - the critical path that was failing dbResult := []map[string]interface{}{ {"created_at": gtimeFromDB}, {"updated_at": gtimeFromDB}, } - + // Convert to time.Time slice (common ORM usage pattern) var timestamps []time.Time err := gconv.Structs(dbResult, ×tamps) t.AssertNil(err) t.Assert(len(timestamps), 2) - + // Verify timezone preservation for both timestamps for i, ts := range timestamps { _, resultOffset := ts.Zone() t.Assert(resultOffset, originalOffset) t.Assert(gtimeFromDB.Time.Equal(ts), true) - - t.Logf("Element %d: %s (offset: %d) - ✅ PRESERVED", + + t.Logf("Element %d: %s (offset: %d) - ✅ PRESERVED", i, ts, resultOffset/3600) } - + // Also test struct field conversion type DatabaseRecord struct { ID int `json:"id"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } - + recordData := map[string]interface{}{ "id": 1, "created_at": gtimeFromDB, "updated_at": gtimeFromDB, } - + var record DatabaseRecord err = gconv.Struct(recordData, &record) t.AssertNil(err) - + _, createdOffset := record.CreatedAt.Zone() _, updatedOffset := record.UpdatedAt.Zone() - + t.Assert(createdOffset, originalOffset) t.Assert(updatedOffset, originalOffset) t.Assert(gtimeFromDB.Time.Equal(record.CreatedAt), true) t.Assert(gtimeFromDB.Time.Equal(record.UpdatedAt), true) - + t.Logf("Struct fields: CreatedAt=%s (offset: %d), UpdatedAt=%s (offset: %d) - ✅ PRESERVED", record.CreatedAt, createdOffset/3600, record.UpdatedAt, updatedOffset/3600) } @@ -205,14 +205,14 @@ func TestGTimeTimezonePreservation_PrecisionAndEdgeCases(t *testing.T) { defer func() { time.Local = originalLocation }() - + // Use London timezone (has DST transitions) londonLocation, _ := time.LoadLocation("Europe/London") time.Local = londonLocation - + // Test precision preservation t.Logf("=== Precision Preservation Tests ===") - + precisionTests := []struct { name string nanos int @@ -223,65 +223,65 @@ func TestGTimeTimezonePreservation_PrecisionAndEdgeCases(t *testing.T) { {"Zero_Nanos", 0}, {"Max_Nanos", 999999999}, } - - for _, pt := range precisionTests { + + for _, pt := range precisionTests { t.Logf("--- Testing %s precision ---", pt.name) - + testTime := time.Date(2025, 9, 16, 11, 32, 42, pt.nanos, time.UTC) gtimeVal := gtime.NewFromTime(testTime) - + // Test through Structs conversion (the problematic path) result := []map[string]interface{}{{"time": gtimeVal}} var timeSlice []time.Time err := gconv.Structs(result, &timeSlice) t.AssertNil(err) t.Assert(len(timeSlice), 1) - + convertedTime := timeSlice[0] t.Assert(convertedTime.Nanosecond(), pt.nanos) t.Assert(convertedTime.Equal(testTime), true) - - t.Logf("%s: Original=%d ns, Converted=%d ns - ✅ PRESERVED", + + t.Logf("%s: Original=%d ns, Converted=%d ns - ✅ PRESERVED", pt.name, pt.nanos, convertedTime.Nanosecond()) } - + // Test edge cases t.Logf("=== Edge Cases Tests ===") - + // Test 1: Leap year leapTime := time.Date(2024, 2, 29, 11, 32, 42, 0, time.UTC) gtimeLeap := gtime.NewFromTime(leapTime) - + var leapResult []time.Time err := gconv.Structs([]map[string]interface{}{{"time": gtimeLeap}}, &leapResult) t.AssertNil(err) t.Assert(leapResult[0].Equal(leapTime), true) t.Logf("Leap year: %s - ✅ PRESERVED", leapResult[0]) - + // Test 2: Year boundaries yearBoundary := time.Date(1999, 12, 31, 23, 59, 59, 999999999, time.UTC) gtimeYear := gtime.NewFromTime(yearBoundary) - + var yearResult []time.Time err = gconv.Structs([]map[string]interface{}{{"time": gtimeYear}}, &yearResult) t.AssertNil(err) t.Assert(yearResult[0].Equal(yearBoundary), true) t.Logf("Year boundary: %s - ✅ PRESERVED", yearResult[0]) - + // Test 3: Unix epoch epochTime := time.Unix(0, 0).UTC() gtimeEpoch := gtime.NewFromTime(epochTime) - + var epochResult []time.Time err = gconv.Structs([]map[string]interface{}{{"time": gtimeEpoch}}, &epochResult) t.AssertNil(err) t.Assert(epochResult[0].Equal(epochTime), true) t.Logf("Unix epoch: %s - ✅ PRESERVED", epochResult[0]) - + // Test 4: Future date futureTime := time.Date(2099, 12, 31, 23, 59, 59, 0, time.UTC) gtimeFuture := gtime.NewFromTime(futureTime) - + var futureResult []time.Time err = gconv.Structs([]map[string]interface{}{{"time": gtimeFuture}}, &futureResult) t.AssertNil(err) @@ -296,17 +296,17 @@ func TestGTimeTimezonePreservation_PerformanceRegression(t *testing.T) { // Create test data utcTime := time.Date(2025, 9, 16, 11, 32, 42, 878465000, time.UTC) gtimeVal := gtime.NewFromTime(utcTime) - + // Performance test: Ensure timezone preservation doesn't significantly impact performance iterations := 1000 - + // Test 1: Direct conversion performance start := time.Now() for i := 0; i < iterations; i++ { _ = gconv.Time(gtimeVal) } directDuration := time.Since(start) - + // Test 2: Struct conversion performance start = time.Now() for i := 0; i < iterations; i++ { @@ -314,7 +314,7 @@ func TestGTimeTimezonePreservation_PerformanceRegression(t *testing.T) { _ = gconv.Struct(gtimeVal, &result) } structDuration := time.Since(start) - + // Test 3: Structs (slice) conversion performance mapData := []map[string]interface{}{{"time": gtimeVal}} start = time.Now() @@ -323,22 +323,22 @@ func TestGTimeTimezonePreservation_PerformanceRegression(t *testing.T) { _ = gconv.Structs(mapData, &result) } sliceDuration := time.Since(start) - + // Performance should be reasonable (not exact assertions, just reasonable bounds) t.Logf("Performance Results for %d iterations:", iterations) t.Logf("Direct conversion: %v (avg: %v/op)", directDuration, directDuration/time.Duration(iterations)) t.Logf("Struct conversion: %v (avg: %v/op)", structDuration, structDuration/time.Duration(iterations)) t.Logf("Slice conversion: %v (avg: %v/op)", sliceDuration, sliceDuration/time.Duration(iterations)) - + // Ensure performance is reasonable (under 1ms per operation) avgDirect := directDuration / time.Duration(iterations) avgStruct := structDuration / time.Duration(iterations) avgSlice := sliceDuration / time.Duration(iterations) - + t.Assert(avgDirect < time.Millisecond, true) t.Assert(avgStruct < time.Millisecond, true) t.Assert(avgSlice < time.Millisecond, true) - + t.Logf("All performance tests passed ✅") }) } @@ -350,4 +350,4 @@ func mustLoadLocationComprehensive(name string) *time.Location { panic(err) } return loc -} \ No newline at end of file +} diff --git a/util/gconv/gconv_z_unit_string_gtime_simple_test.go b/util/gconv/gconv_z_unit_string_gtime_simple_test.go index 6585c9424..382b55a68 100644 --- a/util/gconv/gconv_z_unit_string_gtime_simple_test.go +++ b/util/gconv/gconv_z_unit_string_gtime_simple_test.go @@ -23,38 +23,38 @@ func TestGTimeStringConversion_Basic(t *testing.T) { defer func() { time.Local = originalLocation }() - + parisLocation, _ := time.LoadLocation("Europe/Paris") time.Local = parisLocation - + // Test UTC time string conversion utcTime := time.Date(2025, 9, 16, 11, 32, 42, 878465000, time.UTC) gtimeVal := gtime.NewFromTime(utcTime) - + // Test gtime.Time to string resultStr := gconv.String(*gtimeVal) t.Logf("gtime to string: %s", resultStr) - + // Should use RFC3339 format (note: microseconds will be truncated if they're 0) expectedRFC3339 := "2025-09-16T11:32:42Z" t.Assert(resultStr, expectedRFC3339) - + // Test *gtime.Time to string ptrStr := gconv.String(gtimeVal) t.Assert(ptrStr, expectedRFC3339) - + // Test round-trip conversion reconverted := gconv.GTime(resultStr) t.AssertNE(reconverted, nil) - + // Check if times represent the same instant (more important than exact equality due to precision differences) t.Assert(gtimeVal.Time.Truncate(time.Second).Equal(reconverted.Time.Truncate(time.Second)), true) - + // Verify timezone preservation _, originalOffset := gtimeVal.Zone() _, reconvertedOffset := reconverted.Zone() t.Assert(reconvertedOffset, originalOffset) - + t.Logf("✅ String conversion preserves timezone correctly") }) } @@ -65,23 +65,23 @@ func TestGTimeStringConversion_Precision(t *testing.T) { // Test microsecond precision preciseTime := time.Date(2025, 9, 16, 11, 32, 42, 123456789, time.UTC) gtimeVal := gtime.NewFromTime(preciseTime) - + // Convert to string timeStr := gconv.String(gtimeVal) t.Logf("Precise time string: %s", timeStr) - + // Should include nanosecond precision expected := "2025-09-16T11:32:42.123456789Z" t.Assert(timeStr, expected) - + // Convert back reconverted := gconv.GTime(timeStr) t.AssertNE(reconverted, nil) - + // Verify precision preservation t.Assert(reconverted.Nanosecond(), preciseTime.Nanosecond()) t.Assert(reconverted.Equal(gtimeVal), true) - + t.Logf("✅ Precision preserved in string conversion") }) } @@ -93,23 +93,23 @@ func TestGTimeStringConversion_EdgeCases(t *testing.T) { zeroGTime := gtime.Time{} zeroStr := gconv.String(zeroGTime) t.Assert(zeroStr, "") - + // Test nil gtime var nilGTime *gtime.Time = nil nilStr := gconv.String(nilGTime) t.Assert(nilStr, "") - + // Test very old date oldTime := time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC) oldGTime := gtime.NewFromTime(oldTime) oldStr := gconv.String(oldGTime) expectedOld := "1900-01-01T00:00:00Z" t.Assert(oldStr, expectedOld) - + // Test round-trip for old date fromOld := gconv.GTime(oldStr) t.Assert(fromOld.Equal(oldGTime), true) - + t.Logf("✅ Edge cases handled correctly") }) -} \ No newline at end of file +}