package excel import ( "fmt" "math" "strconv" "strings" "time" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/os/gtime" ) func NormalizeHeader(header string) string { header = strings.ReplaceAll(header, "\uFEFF", "") header = strings.TrimSpace(header) header = strings.TrimRight(header, "**") return strings.ToUpper(strings.TrimSpace(header)) } func ParseString(value string) string { return strings.TrimSpace(value) } func ParseInt(value string) (int, error) { value = strings.TrimSpace(value) if value == "" { return 0, strconv.ErrSyntax } return strconv.Atoi(value) } func ParsePositiveInt(value string) (int, error) { parsed, err := ParseInt(value) if err != nil || parsed <= 0 { return 0, strconv.ErrSyntax } return parsed, nil } func ParseNonNegativeInt(value string) (int, error) { parsed, err := ParseInt(value) if err != nil || parsed < 0 { return 0, strconv.ErrSyntax } return parsed, nil } func ParseBool(value string) (bool, error) { switch strings.ToLower(strings.TrimSpace(value)) { case "1", "true", "yes", "y", "\u662f", "\u5df2\u5b8c\u6210", "\u5b8c\u6210": return true, nil case "0", "false", "no", "n", "\u5426", "\u672a\u5b8c\u6210": return false, nil default: return false, strconv.ErrSyntax } } func ParseDate(value string) (*gtime.Time, error) { parsed, err := parseTimeValue(value) if err != nil { return nil, err } return gtime.NewFromTime(time.Date( parsed.Year(), time.Month(parsed.Month()), parsed.Day(), 0, 0, 0, 0, parsed.Location(), )), nil } func ParseDateTime(value string) (*gtime.Time, error) { return parseTimeValue(value) } func ParseAmount(value string) (float64, error) { value = strings.TrimSpace(value) if value == "" { return 0, strconv.ErrSyntax } value = strings.ReplaceAll(value, ",", "") parsed, err := strconv.ParseFloat(value, 64) if err != nil || parsed < 0 { return 0, strconv.ErrSyntax } return math.Round(parsed*100) / 100, nil } func normalizedValue(value string, typ ColumnType) (string, error) { switch typ { case "", ColumnTypeString: return ParseString(value), nil case ColumnTypeInt: parsed, err := ParseInt(value) if err != nil { return "", err } return strconv.Itoa(parsed), nil case ColumnTypePositiveInt: parsed, err := ParsePositiveInt(value) if err != nil { return "", err } return strconv.Itoa(parsed), nil case ColumnTypeNonNegativeInt: parsed, err := ParseNonNegativeInt(value) if err != nil { return "", err } return strconv.Itoa(parsed), nil case ColumnTypeBool: parsed, err := ParseBool(value) if err != nil { return "", err } return strconv.FormatBool(parsed), nil case ColumnTypeDate: parsed, err := ParseDate(value) if err != nil { return "", err } return parsed.Format("Y-m-d"), nil case ColumnTypeDateTime: parsed, err := ParseDateTime(value) if err != nil { return "", err } return parsed.Format("Y-m-d H:i:s"), nil case ColumnTypeAmount: parsed, err := ParseAmount(value) if err != nil { return "", err } return fmt.Sprintf("%.2f", parsed), nil default: return "", gerror.Newf("unsupported column type: %s", typ) } } func parseTimeValue(value string) (*gtime.Time, error) { value = strings.TrimSpace(value) if value == "" { return nil, strconv.ErrSyntax } if parsed, ok := parseCompactTime(value); ok { return parsed, nil } if parsed, ok := parseLayoutTime(value); ok { return parsed, nil } parsed, err := gtime.StrToTime(value) if err != nil || parsed == nil || parsed.IsZero() || parsed.Year() <= 0 { if err != nil { return nil, err } return nil, strconv.ErrSyntax } return parsed, nil } func parseCompactTime(value string) (*gtime.Time, bool) { if !isDigits(value) { return nil, false } layout := "" switch len(value) { case 8: layout = "20060102" case 12: layout = "200601021504" case 14: layout = "20060102150405" default: return nil, false } parsed, err := time.ParseInLocation(layout, value, time.Local) if err != nil { return nil, false } return gtime.NewFromTime(parsed), true } func parseLayoutTime(value string) (*gtime.Time, bool) { layouts := []string{ time.RFC3339, time.RFC3339Nano, "2006-1-2 15:04:05", "2006-1-2 15:04", "2006/1/2 15:04:05", "2006/1/2 15:04", "2006.1.2 15:04:05", "2006.1.2 15:04", "2006-1-2T15:04:05", "2006-1-2T15:04", "2006-1-2", "2006/1/2", "2006.1.2", } for _, layout := range layouts { parsed, err := time.ParseInLocation(layout, value, time.Local) if err == nil { return gtime.NewFromTime(parsed), true } } return nil, false } func isDigits(value string) bool { for _, r := range value { if r < '0' || r > '9' { return false } } return value != "" }