mirror of
https://gitee.com/milvus-io/milvus.git
synced 2025-12-06 17:18:35 +08:00
issue: https://github.com/milvus-io/milvus/issues/44585 Signed-off-by: zhenshan.cao <zhenshan.cao@zilliz.com>
630 lines
19 KiB
Go
630 lines
19 KiB
Go
package timestamptz
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
// TestValidateAndNormalizeTimestampTz tests the function for string validation and normalization.
|
|
func TestValidateAndNormalizeTimestampTz(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
inputStr string
|
|
defaultTZ string
|
|
expectedOutput string
|
|
expectError bool
|
|
errorContainsMsg string
|
|
}{
|
|
{
|
|
name: "Case 1: TZ-aware (UTC)",
|
|
inputStr: "2024-10-23T15:30:00Z",
|
|
defaultTZ: "Asia/Tokyo",
|
|
expectedOutput: "2024-10-23T15:30:00Z",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Case 2: TZ-aware (Offset)",
|
|
inputStr: "2024-10-23T15:30:00+05:30",
|
|
defaultTZ: "UTC",
|
|
expectedOutput: "2024-10-23T15:30:00+05:30",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Case 3: Naive (Apply Default TZ Shanghai)",
|
|
inputStr: "2024-10-23 15:30:00",
|
|
defaultTZ: "Asia/Shanghai", // Shanghai is UTC+08:00
|
|
expectedOutput: "2024-10-23T15:30:00+08:00",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Case 4: Naive (Apply Default TZ LA)",
|
|
inputStr: "2024-10-23 15:30:00.123456",
|
|
defaultTZ: "America/Los_Angeles", // LA is UTC-07:00 (PDT for Oct)
|
|
expectedOutput: "2024-10-23T15:30:00.123456-07:00",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Case 5: Invalid Format",
|
|
inputStr: "23-10-2024 15:30",
|
|
defaultTZ: "UTC",
|
|
expectedOutput: "",
|
|
expectError: true,
|
|
errorContainsMsg: "invalid timestamp string",
|
|
},
|
|
{
|
|
name: "Case 6: Invalid Default Timezone",
|
|
inputStr: "2024-10-23T15:30:00Z",
|
|
defaultTZ: "Invalid/TZ",
|
|
expectedOutput: "",
|
|
expectError: true,
|
|
errorContainsMsg: "invalid default timezone string",
|
|
},
|
|
{
|
|
name: "Case 7: Offset Too High (+15:00)",
|
|
inputStr: "2024-10-23T15:30:00+15:00",
|
|
defaultTZ: "UTC",
|
|
expectedOutput: "",
|
|
expectError: true,
|
|
errorContainsMsg: "UTC offset hour 15 is out of the valid range",
|
|
},
|
|
{
|
|
name: "Case 8: Offset Too Low (-13:00)",
|
|
inputStr: "2024-10-23T15:30:00-13:00",
|
|
defaultTZ: "UTC",
|
|
expectedOutput: "",
|
|
expectError: true,
|
|
errorContainsMsg: "UTC offset hour -13 is out of the valid range",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
result, err := ValidateAndNormalizeTimestampTz(tc.inputStr, tc.defaultTZ)
|
|
|
|
if tc.expectError {
|
|
assert.Error(t, err)
|
|
if err != nil {
|
|
assert.Contains(t, err.Error(), tc.errorContainsMsg)
|
|
}
|
|
} else {
|
|
assert.NoError(t, err)
|
|
// Truncate expected output to microsecond for safe comparison
|
|
// as time.Time can contain up to nanoseconds, but we test to microsecond precision.
|
|
tParsed, _ := time.Parse(time.RFC3339Nano, tc.expectedOutput)
|
|
expectedFormatted := tParsed.Truncate(time.Microsecond).Format(time.RFC3339Nano)
|
|
|
|
assert.Equal(t, expectedFormatted, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestValidateAndReturnUnixMicroTz tests the function for Unix microsecond conversion.
|
|
func TestValidateAndReturnUnixMicroTz(t *testing.T) {
|
|
// Base time for comparison: 2024-10-23T15:30:00.123456Z (UTC)
|
|
// UnixMicro should be the same regardless of input format/timezone if moment is the same.
|
|
expectedMicro := int64(1729697400123456)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
inputStr string
|
|
defaultTZ string
|
|
expectedMicro int64
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "Case 1: TZ-aware (UTC)",
|
|
inputStr: "2024-10-23T15:30:00.123456Z",
|
|
defaultTZ: "Asia/Tokyo",
|
|
expectedMicro: expectedMicro,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Case 2: TZ-aware (Offset)",
|
|
inputStr: "2024-10-23T23:30:00.123456+08:00", // +8h is 15:30 UTC
|
|
defaultTZ: "UTC",
|
|
expectedMicro: expectedMicro,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Case 3: Naive (Apply Default TZ Shanghai)",
|
|
inputStr: "2024-10-23 23:30:00.123456", // 23:30 Shanghai (UTC+08:00) is 15:30 UTC
|
|
defaultTZ: "Asia/Shanghai",
|
|
expectedMicro: expectedMicro,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Case 4: Naive (Apply Default TZ LA)",
|
|
inputStr: "2024-10-23 08:30:00.123456", // 08:30 LA (PDT, UTC-07:00) is 15:30 UTC
|
|
defaultTZ: "America/Los_Angeles",
|
|
expectedMicro: expectedMicro,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Case 5: Invalid Offset",
|
|
inputStr: "2024-10-23T15:30:00+15:00",
|
|
defaultTZ: "UTC",
|
|
expectedMicro: 0,
|
|
expectError: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
result, err := ValidateAndReturnUnixMicroTz(tc.inputStr, tc.defaultTZ)
|
|
|
|
if tc.expectError {
|
|
assert.Error(t, err)
|
|
assert.Equal(t, int64(0), result)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tc.expectedMicro, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestCompareUnixMicroTz tests the comparison function.
|
|
func TestCompareUnixMicroTz(t *testing.T) {
|
|
// Base time for comparison: 2025-10-23T15:30:00Z (Using the current date for realism)
|
|
|
|
// These strings all represent the moment 2025-10-23T15:30:00.000000Z
|
|
sameTime1 := "2025-10-23T15:30:00Z"
|
|
sameTime2 := "2025-10-23T23:30:00+08:00" // Shanghai: 23:30 local -> 15:30 UTC
|
|
sameTime3 := "2025-10-23 15:30:00" // Naive time
|
|
|
|
// A slightly different moment (1 microsecond later)
|
|
differentTime := "2025-10-23T15:30:00.000001Z"
|
|
|
|
testCases := []struct {
|
|
name string
|
|
ts1 string
|
|
ts2 string
|
|
defaultTZ string
|
|
expectedCmp bool
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "Case 1: Same moment, different explicit TZ format",
|
|
ts1: sameTime1, // 15:30 UTC
|
|
ts2: sameTime2, // 15:30 UTC
|
|
defaultTZ: "UTC",
|
|
expectedCmp: true,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Case 2: Same moment, one naive, UTC default TZ needed",
|
|
ts1: sameTime1, // 15:30 UTC
|
|
ts2: sameTime3, // 15:30 assigned UTC -> 15:30 UTC
|
|
defaultTZ: "UTC",
|
|
expectedCmp: true,
|
|
expectError: false,
|
|
},
|
|
{
|
|
// FIX FOR DST: On 2025-10-23, New York is in EDT (UTC-04:00).
|
|
// To match 15:30 UTC, the naive time (ts2) must be 15:30 + 04:00 = 19:30 local time.
|
|
name: "Case 3: Same moment, DST-aware comparison for naive string",
|
|
ts1: "2025-10-23T19:30:00-04:00", // 19:30 EDT -> 15:30 UTC
|
|
ts2: "2025-10-23 19:30:00", // 19:30 assigned New York (EDT) -> 15:30 UTC
|
|
defaultTZ: "America/New_York",
|
|
expectedCmp: true,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Case 4: Different moment (1 microsecond difference)",
|
|
ts1: sameTime1,
|
|
ts2: differentTime,
|
|
defaultTZ: "UTC",
|
|
expectedCmp: false,
|
|
expectError: false,
|
|
},
|
|
// Revised Case 5
|
|
{
|
|
name: "Case 5: Different naive times under the same default TZ",
|
|
ts1: "2025-10-23 10:00:00", // 10:00 assigned LA -> 03:00 UTC
|
|
ts2: "2025-10-23 11:00:00", // 11:00 assigned LA -> 04:00 UTC
|
|
defaultTZ: "America/Los_Angeles",
|
|
expectedCmp: false, // 03:00 UTC != 04:00 UTC
|
|
expectError: false,
|
|
},
|
|
// New Case 5B: Identical naive strings MUST be equal.
|
|
{
|
|
name: "Case 5B: Identical naive strings must be equal",
|
|
ts1: "2025-10-23 10:00:00", // 10:00 assigned LA -> 03:00 UTC
|
|
ts2: "2025-10-23 10:00:00", // 10:00 assigned LA -> 03:00 UTC
|
|
defaultTZ: "America/Los_Angeles",
|
|
expectedCmp: true,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Case 6: Invalid TS1 (Offset Too High)",
|
|
ts1: "2025-10-23T15:30:00+15:00", // Should fail offset check
|
|
ts2: sameTime1,
|
|
defaultTZ: "UTC",
|
|
expectedCmp: false,
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "Case 7: Invalid TS2 (Bad Format)",
|
|
ts1: sameTime1,
|
|
ts2: "not a timestamp",
|
|
defaultTZ: "UTC",
|
|
expectedCmp: false,
|
|
expectError: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
result, err := CompareUnixMicroTz(tc.ts1, tc.ts2, tc.defaultTZ)
|
|
|
|
if tc.expectError {
|
|
assert.Error(t, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tc.expectedCmp, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestValidateTimestampTz tests the simple validation function.
|
|
func TestValidateTimestampTz(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
inputStr string
|
|
defaultTZ string
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "Case 1: Valid TZ-aware",
|
|
inputStr: "2024-10-23T15:30:00Z",
|
|
defaultTZ: "UTC",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Case 2: Valid Naive",
|
|
inputStr: "2024-10-23 15:30:00",
|
|
defaultTZ: "Asia/Shanghai",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Case 3: Invalid Format",
|
|
inputStr: "Invalid",
|
|
defaultTZ: "UTC",
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "Case 4: Invalid Offset",
|
|
inputStr: "2024-10-23T15:30:00+15:00",
|
|
defaultTZ: "UTC",
|
|
expectError: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
err := ValidateTimestampTz(tc.inputStr, tc.defaultTZ)
|
|
|
|
if tc.expectError {
|
|
assert.Error(t, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestConvertUnixMicroToTimezoneString tests the conversion of Unix Microsecond
|
|
// to a TZ-aware RFC3339Nano string.
|
|
func TestConvertUnixMicroToTimezoneString2(t *testing.T) {
|
|
// Base time for all tests: 2025-03-20T10:30:00.123456Z (UTC)
|
|
// This date is chosen to test standard time outside of DST changes.
|
|
baseTimeUTC := time.Date(2025, time.March, 20, 10, 30, 0, 123456000, time.UTC)
|
|
baseUnixMicro := baseTimeUTC.UnixMicro() // 1710921000123456 (Actual value calculated by Go)
|
|
|
|
// DST test date: 2025-10-23T15:30:00.000000Z (UTC)
|
|
// New York is in EDT (UTC-04:00) on this date.
|
|
dstTimeUTC := time.Date(2025, time.October, 23, 15, 30, 0, 0, time.UTC)
|
|
dstUnixMicro := dstTimeUTC.UnixMicro() // 1729697400000000
|
|
|
|
testCases := []struct {
|
|
name string
|
|
unixMicro int64
|
|
targetTZ string
|
|
expectedOutput string
|
|
expectError bool
|
|
errorContainsMsg string
|
|
}{
|
|
// --- Basic Functionality & Precision ---
|
|
{
|
|
name: "Case 1: Standard conversion to UTC",
|
|
unixMicro: baseUnixMicro,
|
|
targetTZ: "UTC",
|
|
expectedOutput: "2025-03-20T10:30:00.123456Z",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Case 2: Conversion to Asia/Shanghai (+08:00)",
|
|
unixMicro: baseUnixMicro,
|
|
targetTZ: "Asia/Shanghai", // UTC+08:00
|
|
expectedOutput: "2025-03-20T18:30:00.123456+08:00",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Case 3: Conversion to America/Los_Angeles (PDT on Mar 20)",
|
|
unixMicro: baseUnixMicro,
|
|
targetTZ: "America/Los_Angeles",
|
|
// Corrected Output: 10:30 UTC - 7 hours (PDT) = 03:30 local time
|
|
expectedOutput: "2025-03-20T03:30:00.123456-07:00",
|
|
expectError: false,
|
|
},
|
|
|
|
// --- DST Handling (2025-10-23) ---
|
|
{
|
|
name: "Case 4: DST active (America/New_York) - UTC-04:00",
|
|
unixMicro: dstUnixMicro,
|
|
targetTZ: "America/New_York", // EDT = UTC-04:00 on this date
|
|
expectedOutput: "2025-10-23T11:30:00-04:00", // 15:30 UTC -> 11:30 local time
|
|
expectError: false,
|
|
},
|
|
|
|
// --- Error Handling ---
|
|
{
|
|
name: "Case 5: Invalid Timezone String",
|
|
unixMicro: baseUnixMicro,
|
|
targetTZ: "Invalid/TZ_Name",
|
|
expectedOutput: "",
|
|
expectError: true,
|
|
errorContainsMsg: "invalid target timezone string",
|
|
},
|
|
{
|
|
name: "Case 6: UnixMicro 0 (Epoch)",
|
|
unixMicro: 0,
|
|
targetTZ: "UTC",
|
|
expectedOutput: "1970-01-01T00:00:00Z",
|
|
expectError: false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
result, err := ConvertUnixMicroToTimezoneString(tc.unixMicro, tc.targetTZ)
|
|
|
|
if tc.expectError {
|
|
assert.Error(t, err)
|
|
if err != nil {
|
|
assert.Contains(t, err.Error(), tc.errorContainsMsg)
|
|
}
|
|
} else {
|
|
assert.NoError(t, err)
|
|
|
|
// Assert the resulting string can be parsed back and retains microsecond precision
|
|
tParsed, parseErr := time.Parse(time.RFC3339Nano, result)
|
|
assert.NoError(t, parseErr)
|
|
|
|
// Standardize the expected output to ensure nanosecond precision padding is correct
|
|
expectedFormatted := tc.expectedOutput
|
|
|
|
assert.Equal(t, expectedFormatted, result)
|
|
|
|
// Ensure the microsecond value is maintained (conversion consistency check)
|
|
assert.Equal(t, tc.unixMicro, tParsed.UnixMicro())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestFormatTimeMicroWithoutTrailingZerosREVISED tests the custom formatting function
|
|
// to ensure it correctly truncates to microsecond and removes trailing zeros.
|
|
func TestFormatTimeMicroWithoutTrailingZeros(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
nanoSeconds int // Nanoseconds part
|
|
tzOffsetHours int // TZ Offset in hours
|
|
expectedOutput string // Expected final string after cleaning
|
|
}{
|
|
{
|
|
name: "Case 1: Full Microsecond Precision (No trailing zeros to remove)",
|
|
nanoSeconds: 123456000, // .123456
|
|
tzOffsetHours: 8,
|
|
expectedOutput: "2025-10-23T00:00:00.123456+08:00",
|
|
},
|
|
{
|
|
name: "Case 2: Millisecond Precision (Remove 6 trailing zeros)",
|
|
nanoSeconds: 123000000, // .123
|
|
tzOffsetHours: 8,
|
|
expectedOutput: "2025-10-23T00:00:00.123+08:00",
|
|
},
|
|
{
|
|
name: "Case 3: Second Precision (Remove all 9 fractional zeros)",
|
|
nanoSeconds: 0, // .000000
|
|
tzOffsetHours: 8,
|
|
expectedOutput: "2025-10-23T00:00:00+08:00", // Note: The dot is removed.
|
|
},
|
|
{
|
|
name: "Case 4: Single-digit precision (Remove 8 trailing zeros)",
|
|
nanoSeconds: 100000000, // .1
|
|
tzOffsetHours: 0,
|
|
expectedOutput: "2025-10-23T00:00:00.1Z",
|
|
},
|
|
{
|
|
name: "Case 5: Precision beyond microsecond (Truncate first, then remove zeros)",
|
|
nanoSeconds: 123456789, // .123456789 -> truncates to .123456
|
|
tzOffsetHours: -7,
|
|
expectedOutput: "2025-10-23T00:00:00.123456-07:00",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
// Create a time.Time object for a fixed date/time
|
|
tz := time.FixedZone("TEST_TZ", tc.tzOffsetHours*3600)
|
|
tInput := time.Date(2025, time.October, 23, 0, 0, 0, tc.nanoSeconds, tz)
|
|
|
|
result := FormatTimeMicroWithoutTrailingZeros(tInput)
|
|
|
|
assert.Equal(t, tc.expectedOutput, result)
|
|
|
|
// Verification: Ensure round-trip parsing still works and yields the correct microsecond value
|
|
tParsed, parseErr := time.Parse(time.RFC3339Nano, result)
|
|
assert.NoError(t, parseErr)
|
|
// Microsecond precision is preserved
|
|
assert.Equal(t, tInput.Truncate(time.Microsecond).UnixMicro(), tParsed.UnixMicro())
|
|
})
|
|
}
|
|
}
|
|
|
|
// funcutil/time_test.go (Updated TestConvertUnixMicroToTimezoneString)
|
|
|
|
// TestConvertUnixMicroToTimezoneString tests the conversion of Unix Microsecond
|
|
// to a TZ-aware RFC3339Nano string, now including the trailing zero removal logic.
|
|
func TestConvertUnixMicroToTimezoneString(t *testing.T) {
|
|
// Base time 1: 2025-03-20T10:30:00.123456Z (UTC). Nanoseconds: 123456000
|
|
baseTimeUTC := time.Date(2025, time.March, 20, 10, 30, 0, 123456000, time.UTC)
|
|
baseUnixMicro := baseTimeUTC.UnixMicro() // 1710921000123456
|
|
|
|
// Base time 2: 2025-10-23T15:30:00.000000Z (UTC). Nanoseconds: 0
|
|
dstTimeUTC := time.Date(2025, time.October, 23, 15, 30, 0, 0, time.UTC)
|
|
dstUnixMicro := dstTimeUTC.UnixMicro() // 1729697400000000
|
|
|
|
testCases := []struct {
|
|
name string
|
|
unixMicro int64
|
|
targetTZ string
|
|
expectedOutput string // NOW CLEANED OF TRAILING ZEROS
|
|
expectError bool
|
|
errorContainsMsg string
|
|
}{
|
|
// --- Basic Functionality & Precision (Using baseUnixMicro - ends in .123456) ---
|
|
{
|
|
name: "Case 1: Standard conversion to UTC (Microsecond precision)",
|
|
unixMicro: baseUnixMicro,
|
|
targetTZ: "UTC",
|
|
expectedOutput: "2025-03-20T10:30:00.123456Z", // Removed "000"
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Case 2: Conversion to Asia/Shanghai (+08:00)",
|
|
unixMicro: baseUnixMicro,
|
|
targetTZ: "Asia/Shanghai", // UTC+08:00
|
|
expectedOutput: "2025-03-20T18:30:00.123456+08:00", // Removed "000"
|
|
expectError: false,
|
|
},
|
|
{
|
|
// Fix from previous round: 10:30 UTC - 7 hours (PDT) = 03:30 local time.
|
|
name: "Case 3: Conversion to America/Los_Angeles (PDT on Mar 20)",
|
|
unixMicro: baseUnixMicro,
|
|
targetTZ: "America/Los_Angeles", // PDT = UTC-07:00
|
|
expectedOutput: "2025-03-20T03:30:00.123456-07:00", // Removed "000"
|
|
expectError: false,
|
|
},
|
|
|
|
// --- DST Handling (Using dstUnixMicro - ends in .000000) ---
|
|
{
|
|
name: "Case 4: DST active (America/New_York) - UTC-04:00 (Second precision)",
|
|
unixMicro: dstUnixMicro,
|
|
targetTZ: "America/New_York", // EDT = UTC-04:00 on this date
|
|
expectedOutput: "2025-10-23T11:30:00-04:00", // Removed all fractional zeros and the dot
|
|
expectError: false,
|
|
},
|
|
|
|
// --- Custom Test Case: Only Millisecond precision (must remove 3 trailing zeros) ---
|
|
{
|
|
name: "Case 5: Millisecond precision input",
|
|
// Use the calculated value for 2025-03-20T10:30:00.123Z.
|
|
// If you were previously using baseUnixMicro, calculate the new value:
|
|
// (baseUnixMicro / 1000000) * 1000000 + 123000
|
|
unixMicro: (baseUnixMicro / 1000000 * 1000000) + 123000,
|
|
targetTZ: "UTC",
|
|
expectedOutput: "2025-03-20T10:30:00.123Z",
|
|
expectError: false,
|
|
},
|
|
|
|
// --- Error Handling ---
|
|
{
|
|
name: "Case 6: Invalid Timezone String",
|
|
unixMicro: baseUnixMicro,
|
|
targetTZ: "Invalid/TZ_Name",
|
|
expectedOutput: "",
|
|
expectError: true,
|
|
errorContainsMsg: "invalid target timezone string",
|
|
},
|
|
{
|
|
name: "Case 7: UnixMicro 0 (Epoch) - Second precision",
|
|
unixMicro: 0,
|
|
targetTZ: "UTC",
|
|
expectedOutput: "1970-01-01T00:00:00Z", // Removed all fractional zeros and the dot
|
|
expectError: false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
result, err := ConvertUnixMicroToTimezoneString(tc.unixMicro, tc.targetTZ)
|
|
|
|
if tc.expectError {
|
|
assert.Error(t, err)
|
|
if err != nil {
|
|
assert.Contains(t, err.Error(), tc.errorContainsMsg)
|
|
}
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tc.expectedOutput, result)
|
|
|
|
// Verify round-trip consistency: the resulting string must parse back
|
|
// to the original microsecond value (since no significant digits were lost).
|
|
tParsed, parseErr := time.Parse(time.RFC3339Nano, result)
|
|
assert.NoError(t, parseErr)
|
|
assert.Equal(t, tc.unixMicro, tParsed.UnixMicro())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestIsTimezoneValid tests the IsTimezoneValid function.
|
|
func TestIsTimezoneValid(t *testing.T) {
|
|
// Common valid IANA timezone names
|
|
validTimezones := []string{
|
|
"UTC",
|
|
"Local",
|
|
"Asia/Shanghai",
|
|
"America/New_York",
|
|
"Europe/London",
|
|
"Australia/Sydney",
|
|
"Etc/GMT+10",
|
|
}
|
|
|
|
// Common invalid or malformed timezone names
|
|
invalidTimezones := []string{
|
|
"",
|
|
"GMT+8",
|
|
"CST",
|
|
"PST",
|
|
"Invalid/Zone",
|
|
"Asia/Beijingg",
|
|
"99:00",
|
|
time.FixedZone("MyZone", 3600).String(), // Custom location names (like "MyZone") are not recognized by time.LoadLocation.
|
|
}
|
|
|
|
// Test valid timezones
|
|
for _, tz := range validTimezones {
|
|
t.Run("Valid_"+tz, func(t *testing.T) {
|
|
if !IsTimezoneValid(tz) {
|
|
t.Errorf("IsTimezoneValid(\"%s\") expected true, but got false", tz)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Test invalid timezones
|
|
for _, tz := range invalidTimezones {
|
|
t.Run("Invalid_"+tz, func(t *testing.T) {
|
|
if IsTimezoneValid(tz) {
|
|
t.Errorf("IsTimezoneValid(\"%s\") expected false, but got true", tz)
|
|
}
|
|
})
|
|
}
|
|
}
|