Golang解析未知格式字符串为时间的实现方法

Golang解析未知格式字符串为时间的实现方法 我有一个 csv 文件,想要读取:

  1. 表头名称
  2. 字段类型

因此,我编写了以下代码:

package main

import (
	"encoding/csv"
	"fmt"
	"os"
    "log"
	"reflect"
	"strconv"
)

func main() {
	filePath := "./file.csv"
	headerNames := make(map[int]string)
	headerTypes := make(map[int]string)
	// Load a csv file.
	f, _ := os.Open(filePath)
	// Create a new reader.
	r := csv.NewReader(f)
	// Read first row only
	header, err := r.Read()
	checkError("Some other error occurred", err)

	// Add mapping: Column/property name --> record index
	for i, v := range header {
		headerNames[i] = v
	}

	// Read second row
	record, err := r.Read()
	checkError("Some other error occurred", err)
	// Check record fields types
	for i, v := range record {
		var value interface{}
		if value, err = strconv.Atoi(v); err != nil {
			if value, err = strconv.ParseFloat(v, 64); err != nil {
				if value, err = strconv.ParseBool(v); err != nil {
					if value, err = strconv.ParseBool(v); err != nil { // <== How to do this with unknown layout
						// Value is a string
						headerTypes[i] = "string"
						value = v
						fmt.Println(reflect.TypeOf(value), reflect.ValueOf(value))
					} else {
						// Value is a timestamp
						headerTypes[i] = "time"
						fmt.Println(reflect.TypeOf(value), reflect.ValueOf(value))
					}
				} else {
					// Value is a bool
					headerTypes[i] = "bool"
					fmt.Println(reflect.TypeOf(value), reflect.ValueOf(value))
				}
			} else {
				// Value is a float
				headerTypes[i] = "float"
				fmt.Println(reflect.TypeOf(value), reflect.ValueOf(value))
			}
		} else {
			// Value is an int
			headerTypes[i] = "int"
			fmt.Println(reflect.TypeOf(value), reflect.ValueOf(value))
		}
	}

	for i, _ := range header {
		fmt.Printf("Header: %v \tis\t %v\n", headerNames[i], headerTypes[i])
	}
}

func checkError(message string, err error) {
	// Error Logging
	if err != nil {
		log.Fatal(message, err)
	}
}

使用的 csv 文件如下:

name,age,developer
"Hasan","46.4","true"

我得到的输出是:

Header: name       is  string
Header: age        is  float
Header: developer  is  bool

输出是正确的。

我无法做到的是检查字段是否为字符串,因为我不知道字段可能是什么格式。

我知道可以根据 https://go.dev/src/time/format.go 中规定的格式将字符串解析为时间,并且可以构建一个自定义解析器,类似这样:

    test, err := fmtdate.Parse("MM/DD/YYYY", "10/15/1983")
    if err != nil {
        panic(err)
    }

但据我所知,这只有在我知道格式的情况下才有效?

所以,我的问题再次是,如果我不知道格式,我该如何解析时间,或者我应该怎么做才能解析它?


更多关于Golang解析未知格式字符串为时间的实现方法的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

非常感谢

更多关于Golang解析未知格式字符串为时间的实现方法的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


如果不了解输入字符串的布局,就无法解析日期。

请看 1.8.81。很明显这个日期大约在 41 年前(这一点甚至还有待商榷),但它指的是 8 月 1 日还是 1 月 8 日呢?

请查看

GitHub

GitHub - araddon/dateparse: GoLang Parse many date strings without knowing…

GoLang 解析许多日期字符串,而无需提前知道格式。 - GitHub - araddon/dateparse: GoLang 解析许多日期字符串,而无需提前知道格式。

对于解析未知格式的时间字符串,可以使用time.Parse尝试多种预定义布局。以下是一个实现示例:

package main

import (
    "encoding/csv"
    "fmt"
    "os"
    "time"
    "strings"
)

var timeLayouts = []string{
    time.RFC3339,
    time.RFC3339Nano,
    "2006-01-02 15:04:05",
    "2006-01-02",
    "02/01/2006",
    "01/02/2006",
    "2006/01/02",
    "15:04:05",
    "3:04 PM",
    time.RFC822,
    time.RFC822Z,
    time.RFC850,
    time.RFC1123,
    time.RFC1123Z,
    time.ANSIC,
    time.UnixDate,
    time.RubyDate,
    time.Kitchen,
    time.Stamp,
    time.StampMilli,
    time.StampMicro,
    time.StampNano,
}

func parseTime(value string) (time.Time, bool) {
    for _, layout := range timeLayouts {
        if t, err := time.Parse(layout, value); err == nil {
            return t, true
        }
    }
    return time.Time{}, false
}

func main() {
    filePath := "./file.csv"
    f, _ := os.Open(filePath)
    r := csv.NewReader(f)
    
    header, _ := r.Read()
    record, _ := r.Read()
    
    for i, v := range record {
        fmt.Printf("Field: %s, Value: %s\n", header[i], v)
        
        if t, ok := parseTime(v); ok {
            fmt.Printf("  Detected as time: %v (layout found)\n", t)
        } else if _, err := strconv.Atoi(v); err == nil {
            fmt.Printf("  Detected as int\n")
        } else if _, err := strconv.ParseFloat(v, 64); err == nil {
            fmt.Printf("  Detected as float\n")
        } else if _, err := strconv.ParseBool(v); err == nil {
            fmt.Printf("  Detected as bool\n")
        } else {
            fmt.Printf("  Detected as string\n")
        }
    }
}

对于更复杂的情况,可以添加启发式检测:

func isLikelyTime(value string) bool {
    // 移除引号
    v := strings.Trim(value, `"'`)
    
    // 检查常见时间模式
    patterns := []string{
        `^\d{4}-\d{2}-\d{2}`,
        `^\d{2}/\d{2}/\d{4}`,
        `^\d{4}/\d{2}/\d{2}`,
        `^\d{2}-\d{2}-\d{4}`,
        `^\d{2}:\d{2}:\d{2}`,
        `^\d{1,2}:\d{2}\s*(AM|PM|am|pm)`,
    }
    
    for _, pattern := range patterns {
        matched, _ := regexp.MatchString(pattern, v)
        if matched {
            return true
        }
    }
    return false
}

func detectType(value string) string {
    v := strings.Trim(value, `"'`)
    
    // 尝试时间解析
    if _, ok := parseTime(v); ok {
        return "time"
    }
    
    // 尝试数值类型
    if _, err := strconv.Atoi(v); err == nil {
        return "int"
    }
    
    if _, err := strconv.ParseFloat(v, 64); err == nil {
        return "float"
    }
    
    if _, err := strconv.ParseBool(v); err == nil {
        return "bool"
    }
    
    // 启发式时间检测
    if isLikelyTime(v) {
        return "time_likely"
    }
    
    return "string"
}

使用第三方库可以获得更好的时间解析:

import (
    "github.com/araddon/dateparse"
)

func parseTimeWithDateparse(value string) (time.Time, error) {
    return dateparse.ParseAny(value)
}

// 在类型检测中使用
func detectTypeWithLib(value string) string {
    v := strings.Trim(value, `"'`)
    
    if t, err := dateparse.ParseAny(v); err == nil {
        // 验证确实是有效时间
        if !t.IsZero() {
            return "time"
        }
    }
    
    // 其他类型检测...
    return "string"
}

对于CSV处理,完整的类型检测函数:

func detectFieldType(value string) string {
    v := strings.TrimSpace(value)
    
    // 1. 布尔值检测
    if strings.EqualFold(v, "true") || strings.EqualFold(v, "false") {
        if _, err := strconv.ParseBool(v); err == nil {
            return "bool"
        }
    }
    
    // 2. 整数检测
    if _, err := strconv.ParseInt(v, 10, 64); err == nil {
        return "int"
    }
    
    // 3. 浮点数检测
    if _, err := strconv.ParseFloat(v, 64); err == nil {
        return "float"
    }
    
    // 4. 时间检测 - 尝试多种布局
    for _, layout := range timeLayouts {
        if _, err := time.Parse(layout, v); err == nil {
            return "time"
        }
    }
    
    // 5. 日期时间启发式检测
    if matched, _ := regexp.MatchString(`\d{4}[-/]\d{2}[-/]\d{2}`, v); matched {
        return "time_likely"
    }
    
    if matched, _ := regexp.MatchString(`\d{2}[-/]\d{2}[-/]\d{4}`, v); matched {
        return "time_likely"
    }
    
    if matched, _ := regexp.MatchString(`\d{1,2}:\d{2}(:\d{2})?(\s*(AM|PM|am|pm))?`, v); matched {
        return "time_likely"
    }
    
    return "string"
}

这些方法通过尝试多种已知时间格式和启发式规则来检测时间类型,即使不知道确切格式也能处理常见的时间字符串。

回到顶部