Go第三方库之Cast
好久没有更新了,最近一直忙着工作,还有毕业的事情。再加上最近心态上的一些变化,导致博客更新没有像之前在学校那样频繁了。现在趁着这篇文章,试着开始重新更新博客,记录自己的学习经历和一些心路历程。
之前在工作中,看见项目在其他类型转string的时候没有用Go的标准库Strconv
,而是使用了cast
包。点进去看了下源码发现还是挺简单的,这里简单记录一下。
Quick Start
cast的使用非常简单:
1 | package main |
实际上,cast实现了多种常见类型之间的相互转换,返回最符合直觉的结果。例如:
nil
转为string的结果为""
,而不是nil
true
转为string的结果为"true"
,而true
转为int的结果为1
any
转为其他类型,要看它里面存储的值类型
cast支持的类型转换包括所有的基本类型(整形、浮点型、布尔值和字符串)、空接口、nil,时间(time.Time
)、时长(time.Duration
)以及它们的切片类型,还有map[string]Type
,其中Type为:
1 | byte bool float32 float64 string |
cast提供了两个系列的转换:
- ToType(其中Type可以为任何支持的类型),将参数转换为Type类型。如果无法转换,返回Type类型的零值或
nil
- ToTypeE以 E 结尾,返回转换后的值和一个error。这组函数可以区分参数中实际存储了零值,还是转换失败了。
实现上在cast源码中,ToType在内部调用ToTypeE函数,返回结果并忽略错误。ToType函数的实现在文件cast.go中,而ToTypeE函数的实现在文件caste.go中。
1
2
3
4
5 // ToInt casts an interface to an int type.
func ToInt(i interface{}) int {
v, _ := ToIntE(i)
return v
}
更多cast的用法和解析,查看cast源码即可。
源码分析
cast的源码还是很简单的。上面我们说了cast主要有两个系列的函数,其中ToType
在内部调用ToTypeE
函数,返回结果并忽略错误。阅读源码我们可以发现,ToType
函数的实现在文件cast.go中,而ToTypeE
函数的实现在文件caste.go中。
indirect函数
以ToInt & ToIntE为例。
cast.go:
1 | // ToInt casts an interface to an int type. |
caste.go:
1 | // ToIntE casts an interface to an int type. |
观察源码可以发现,在断言之前,都会有i = indirect(i)
这样的操作。
indirect函数函数功能:将参数中可能的指针去掉。如果类型本身不是指针,那么直接返回。否则返回指针指向的值。循环直到返回一个非指针的值:
1 | // From html/template/content.go |
indirect函数处理含有嵌套指针的情况,将最终指向的非指针值返回。
看个例子:
1 | func main() { |
时间和时长转换
ToTime
cast.go:
1 | // ToTime casts an interface to a time.Time type. |
caste.go:
1 | // ToTimeE casts an interface to a time.Time type. |
根据传入的类型执行不同的处理:
-
如果是
time.Time
,直接返回 -
如果是字符串,调用StringToDate函数依次尝试以下面这些时间格式调用time.Parse()解析该字符串。如果某个格式解析成功,则返回获得的time.Time。否则解析失败,返回错误,其他任何类型都无法转换为time.Time
字符串转时间的具体代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64...
case string:
return StringToDateInDefaultLocation(v, location)
...
var (
timeFormats = []timeFormat{
// Keep common formats at the top.
{"2006-01-02", timeFormatNoTimezone},
{time.RFC3339, timeFormatNumericTimezone},
{"2006-01-02T15:04:05", timeFormatNoTimezone}, // iso8601 without timezone
{time.RFC1123Z, timeFormatNumericTimezone},
{time.RFC1123, timeFormatNamedTimezone},
{time.RFC822Z, timeFormatNumericTimezone},
{time.RFC822, timeFormatNamedTimezone},
{time.RFC850, timeFormatNamedTimezone},
{"2006-01-02 15:04:05.999999999 -0700 MST", timeFormatNumericAndNamedTimezone}, // Time.String()
{"2006-01-02T15:04:05-0700", timeFormatNumericTimezone}, // RFC3339 without timezone hh:mm colon
{"2006-01-02 15:04:05Z0700", timeFormatNumericTimezone}, // RFC3339 without T or timezone hh:mm colon
{"2006-01-02 15:04:05", timeFormatNoTimezone},
{time.ANSIC, timeFormatNoTimezone},
{time.UnixDate, timeFormatNamedTimezone},
{time.RubyDate, timeFormatNumericTimezone},
{"2006-01-02 15:04:05Z07:00", timeFormatNumericTimezone},
{"02 Jan 2006", timeFormatNoTimezone},
{"2006-01-02 15:04:05 -07:00", timeFormatNumericTimezone},
{"2006-01-02 15:04:05 -0700", timeFormatNumericTimezone},
{time.Kitchen, timeFormatTimeOnly},
{time.Stamp, timeFormatTimeOnly},
{time.StampMilli, timeFormatTimeOnly},
{time.StampMicro, timeFormatTimeOnly},
{time.StampNano, timeFormatTimeOnly},
}
)
// StringToDateInDefaultLocation casts an empty interface to a time.Time,
// interpreting inputs without a timezone to be in the given location,
// or the local timezone if nil.
func StringToDateInDefaultLocation(s string, location *time.Location) (time.Time, error) {
return parseDateWith(s, location, timeFormats)
}
func parseDateWith(s string, location *time.Location, formats []timeFormat) (d time.Time, e error) {
for _, format := range formats {
if d, e = time.Parse(format.format, s); e == nil {
// Some time formats have a zone name, but no offset, so it gets
// put in that zone name (not the default one passed in to us), but
// without that zone's offset. So set the location manually.
if format.typ <= timeFormatNamedTimezone {
if location == nil {
location = time.Local
}
year, month, day := d.Date()
hour, min, sec := d.Clock()
d = time.Date(year, month, day, hour, min, sec, d.Nanosecond(), location)
}
return
}
}
return d, fmt.Errorf("unable to parse date: %s", s)
} -
如果是整型,将参数作为时间戳(自 UTC 时间1970.01.01 00:00:00到现在的秒数)调用
time.Unix
生成时间。Unix()函数接受两个参数,第一个参数指定秒,第二个参数指定纳秒
举个例子:
1 | func main() { |
ToDuration
cast.go:
1 | // ToDuration casts an interface to a time.Duration type. |
caste.go:
1 | // ToTimeInDefaultLocationE casts an empty interface to time.Time, |
根据传入的类型进行不同的处理:
- 如果是time.Duration类型,直接返回;
- 如果是整型或浮点型,将其数值强制转换为time.Duration类型,单位默认为ns;
- 如果是字符串,分为两种情况:
- 如果字符串中有时间单位符号nsuµmh,直接调用time.ParseDuration解析;
- 否则在字符串后拼接ns再调用time.ParseDuration解析;
- 其他类型解析失败。
实践推荐
- 涉及到时间的转换,最好使用Go标准库
Strconv
- cast只是图方便使用,比如在cast.ToString(),如果确定两种类型的转换,最好使用Go标准库
Strconv
。