yao/openapi/utils/convert.go
Max 8786aa9a6a feat(database): add PostgreSQL support and enhance JSON handling
- Updated database configuration to include PostgreSQL 14.0 in CI workflows.
- Enhanced JSON null detection in the Xun store to support PostgreSQL dialect.
- Refactored time conversion utilities to handle multiple database formats, including PostgreSQL.
- Improved cleanup logic in the DataCleaner to ensure proper order of operations for referential integrity.
- Added utility functions for converting between Unix timestamps and database timestamps.
2026-04-06 02:56:04 +08:00

366 lines
7.7 KiB
Go

package utils
import (
"fmt"
"strconv"
"strings"
"time"
)
// Type Conversion Utilities
// These functions provide safe type conversion from interface{} to common types
// ToBool converts various types to boolean
// Supports: bool, int, int64, float64, string
// String values: "true", "false", "1", "0", "enabled", "disabled", "yes", "no", "on", "off"
// Returns false for nil or unsupported types
func ToBool(v interface{}) bool {
if v == nil {
return false
}
switch val := v.(type) {
case bool:
return val
case int:
return val != 0
case int64:
return val != 0
case float64:
return val != 0
case string:
// Normalize string to lowercase for case-insensitive comparison
normalized := strings.ToLower(strings.TrimSpace(val))
switch normalized {
case "true", "1", "enabled", "yes", "on":
return true
case "false", "0", "disabled", "no", "off", "":
return false
default:
return false
}
default:
return false
}
}
// ToString converts various types to string
// Supports: string, int, int64, float64, bool, time.Time, *time.Time
// time.Time is formatted using the optional timeFormat parameter
// If timeFormat is not provided, defaults to "2006-01-02 15:04:05"
// Returns empty string for nil or unsupported types
func ToString(v interface{}, timeFormat ...string) string {
if v == nil {
return ""
}
// Get time format (default or provided)
format := "2006-01-02 15:04:05"
if len(timeFormat) > 0 && timeFormat[0] != "" {
format = timeFormat[0]
}
switch val := v.(type) {
case string:
return val
case int:
return fmt.Sprintf("%d", val)
case int64:
return fmt.Sprintf("%d", val)
case float64:
return fmt.Sprintf("%.0f", val)
case bool:
if val {
return "true"
}
return "false"
case time.Time:
return val.Format(format)
case *time.Time:
if val != nil {
return val.Format(format)
}
return ""
default:
return ""
}
}
// ToInt64 converts various types to int64
// Supports: int, int64, float64, string
// Returns 0 for nil or unsupported types
func ToInt64(v interface{}) int64 {
if v == nil {
return 0
}
switch val := v.(type) {
case int64:
return val
case int:
return int64(val)
case float64:
return int64(val)
case string:
if parsed, err := strconv.ParseInt(val, 10, 64); err == nil {
return parsed
}
return 0
default:
return 0
}
}
// ToInt converts various types to int
// Supports: int, int64, float64, string
// Returns 0 for nil or unsupported types
func ToInt(v interface{}) int {
if v == nil {
return 0
}
switch val := v.(type) {
case int:
return val
case int64:
return int(val)
case float64:
return int(val)
case string:
if parsed, err := strconv.Atoi(val); err == nil {
return parsed
}
return 0
default:
return 0
}
}
// ToFloat64 converts various types to float64
// Supports: float64, int, int64, string
// Returns 0.0 for nil or unsupported types
func ToFloat64(v interface{}) float64 {
if v == nil {
return 0.0
}
switch val := v.(type) {
case float64:
return val
case float32:
return float64(val)
case int:
return float64(val)
case int64:
return float64(val)
case string:
if parsed, err := strconv.ParseFloat(val, 64); err == nil {
return parsed
}
return 0.0
default:
return 0.0
}
}
// ToTimeString converts various time types to RFC3339 string
// Supports: time.Time, string, int64 (unix timestamp)
// Returns empty string for nil or unsupported types
func ToTimeString(v interface{}) string {
if v == nil {
return ""
}
switch val := v.(type) {
case time.Time:
if val.IsZero() {
return ""
}
return val.Format(time.RFC3339)
case string:
for _, format := range DBTimeFormats {
if t, err := time.Parse(format, val); err == nil {
return t.Format(time.RFC3339)
}
}
return val
case int64:
// Assume unix timestamp
if val > 0 {
return time.Unix(val, 0).Format(time.RFC3339)
}
return ""
default:
return ""
}
}
// GetTimeFormat returns the appropriate time format string for the given locale
// Returns format suitable for time.Format()
func GetTimeFormat(locale string) string {
// Normalize locale to lowercase
locale = strings.ToLower(strings.TrimSpace(locale))
switch locale {
case "zh-cn", "zh":
// Chinese format: 2025年10月30日 08:57:51
return "2006年01月02日 15:04:05"
case "en", "en-us", "":
// English format: October 30, 2025 08:57:51
return "January 02, 2006 15:04:05"
default:
// Default ISO format
return "2006-01-02 15:04:05"
}
}
// DBTimeFormats contains all time formats recognized by database drivers (MySQL, PostgreSQL, SQLite).
// Ordered from most specific to least specific for efficient parsing.
var DBTimeFormats = []string{
"2006-01-02 15:04:05",
"2006-01-02 15:04:05.999999",
"2006-01-02 15:04:05.999999-07",
"2006-01-02 15:04:05.999999+00",
"2006-01-02T15:04:05Z",
"2006-01-02T15:04:05",
"2006-01-02T15:04:05.000Z",
time.RFC3339,
time.RFC3339Nano,
}
// DBTimestampToUnix converts a database timestamp string (interface{}) to a Unix timestamp (int64).
// Supports MySQL DATETIME, PostgreSQL timestamptz (fractional seconds, timezone offsets),
// ISO 8601, RFC3339, and RFC3339Nano. Returns nil for nil, empty, or unparseable input.
func DBTimestampToUnix(val interface{}) interface{} {
if val == nil {
return nil
}
var dateTime string
switch v := val.(type) {
case string:
dateTime = v
case *string:
if v == nil {
return nil
}
dateTime = *v
default:
return nil
}
if dateTime == "" {
return nil
}
for _, format := range DBTimeFormats {
if t, err := time.Parse(format, dateTime); err == nil {
return t.Unix()
}
}
return nil
}
// UnixToDBTimestamp converts a Unix timestamp (int64, *int64, int, float64) to a standard
// DATETIME string "2006-01-02 15:04:05" in UTC. Compatible with MySQL, PostgreSQL, and SQLite.
// Returns nil for nil or unsupported types.
func UnixToDBTimestamp(val interface{}) interface{} {
if val == nil {
return nil
}
var unixTime int64
switch v := val.(type) {
case int64:
unixTime = v
case *int64:
if v == nil {
return nil
}
unixTime = *v
case int:
unixTime = int64(v)
case float64:
unixTime = int64(v)
default:
return nil
}
return time.Unix(unixTime, 0).UTC().Format("2006-01-02 15:04:05")
}
// ToUnixTimestamp converts any interface{} value to a Unix timestamp (int64).
// Handles numeric types directly and parses string/DB timestamp formats via DBTimestampToUnix.
// Returns nil for nil or unsupported types.
func ToUnixTimestamp(val interface{}) interface{} {
if val == nil {
return nil
}
switch v := val.(type) {
case int64:
return v
case *int64:
if v == nil {
return nil
}
return *v
case int:
return int64(v)
case float64:
return int64(v)
case string:
return DBTimestampToUnix(v)
case *string:
if v == nil {
return nil
}
return DBTimestampToUnix(*v)
default:
return nil
}
}
// FormatTimeWithLocale formats a time value (time.Time, *time.Time, or string) using the specified format
// If the input is already a string, it will parse it first and then reformat it
// Returns empty string if the value cannot be parsed
func FormatTimeWithLocale(v interface{}, targetFormat string) string {
if v == nil {
return ""
}
var t time.Time
var err error
switch val := v.(type) {
case time.Time:
t = val
case *time.Time:
if val != nil {
t = *val
} else {
return ""
}
case string:
for _, format := range DBTimeFormats {
t, err = time.Parse(format, val)
if err == nil {
break
}
}
if err != nil {
return val
}
default:
// For unsupported types, try ToString first
str := ToString(v)
if str == "" {
return ""
}
// Try parsing the string
return FormatTimeWithLocale(str, targetFormat)
}
// Format with target format
return t.Format(targetFormat)
}