Golang解析未知格式字符串为时间的实现方法
Golang解析未知格式字符串为时间的实现方法
我有一个 csv 文件,想要读取:
- 表头名称
- 字段类型
因此,我编写了以下代码:
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
如果不了解输入字符串的布局,就无法解析日期。
请看 1.8.81。很明显这个日期大约在 41 年前(这一点甚至还有待商榷),但它指的是 8 月 1 日还是 1 月 8 日呢?
请查看
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"
}
这些方法通过尝试多种已知时间格式和启发式规则来检测时间类型,即使不知道确切格式也能处理常见的时间字符串。

