GoLand(五)-标准库
Json操作
JSON序列化
type class struct {
Title string
Students []Student
}
//...创建class --赋值
{
//JSON序列化:
data, err:= json.Marshal(变量名)
if err !=nil{
fmt.Println("err is",err)
}
}
JSON反序列化
jsonStr := `...数据`
json.Unmarshal([]byte(jsonStr),&结构体)
反射
standard library
OS
文件目录相关
os标准库实现了平台(操作系统)无关的编程接口
os.Create("xxxx")创建文件os.Mkdir("xxxx",os.ModePerm)创建目录os.MkdirAll("xxxx",os.ModePerm)创建多个目录os.Remove("xxxx")删除文件、目录os.RemoveAll("xxxx")删除全部os.Getwd()获得当前路径os.Chdir("d:/")修改路径os.TempDir()临时目录os.Rename("test.txt", "a.txt")文件重命名os.ReadFile("test.txt")文件的读--返回的是字节的切片os.WriteFile("test2.txt", []byte("hello"), os.ModePerm)文件的写
package os
import (
"fmt"
"os"
)
//创建文件
func CreateFile() {
f, err := os.Create("a.txt")
if err != nil {
fmt.Printf("err: %v\n", err)
} else {
fmt.Printf("f.Name(): %v\n", f.Name())
}
}
// 创建目录
func MakerDir() {
//os.ModePerm ---最高权限 777
err := os.Mkdir("test", os.ModePerm)
if err != nil {
fmt.Printf("err: %v\n", err)
}
//创建多个目录
err2 := os.MkdirAll("a/b/c", os.ModePerm)
if err2 != nil {
fmt.Printf("err2: %v\n", err2)
}
}
// 删除目录、文件
func Remove() {
//删除文件
err := os.Remove("a.text")
if err != nil {
fmt.Printf("err: %v\n", err)
}
//删除全部
err2 := os.RemoveAll("a")
if err2 != nil {
fmt.Printf("err2: %v\n", err2)
}
}
// 获得工作目录-修改目录-获得当前的临时目录
func Pwd() {
dir, err := os.Getwd()
if err != nil {
fmt.Printf("err: %v\n", err)
}
fmt.Printf("dir: %v\n", dir)
//修改目录
os.Chdir("d:/")
dir, err2 := os.Getwd()
if err2 != nil {
fmt.Printf("err: %v\n", err)
}
fmt.Printf("dir: %v\n", dir)
//临时目录
s := os.TempDir()
fmt.Printf("s: %v\n", s)
/**
dir: D:\桌面\GOlearn\learn
dir: d:\
s: C:\Users\郝泾钊\AppData\Local\Temp
*/
}
// 重名名文件名
func Rename() {
err := os.Rename("test.txt", "a.txt")
if err != nil {
fmt.Printf("err: %v\n", err)
}
}
// 文件的读
func ReadFile() {
//文件返回的是字符的切片
b, err := os.ReadFile("test.txt")
fmt.Printf("b: %v\n", string(b[:]))
if err != nil {
fmt.Printf("err: %v\n", err)
}
}
// 写文件
func WriteFile() {
os.WriteFile("test2.txt", []byte("hello"), os.ModePerm)
}
File文件读操作
这里结束和File结构体相关的文件读操作
os.Open("a.txt")打开文件--等价于--os.OpenFile(name.O_RDONLY,0)os.OpenFile("a.txt", os.O_RDWR|os.O_CREATE, 0755)打开文件os.Create("a2.txt")创建文件----等价于---OpenFile(name,O_RDWR|O_CREATE|O_TRUNC,0666)os.CreateTemp("", "temp")创建默认目录f, err := os.Open("a.txt")---创建缓存区--f.Read(buf)-循环读取 文件读取f.ReadAt(buf, 5)从第5个字节开始读取 文件读取f.ReadDir(-1)读取目录f.Seek(3, 0)定位
package os
import (
"fmt"
"io"
"os"
)
//文件的读操作
// 打开关闭文件
func OpenCloseFile() {
//只能读
//第一种:
f, err := os.Open("a.txt")
//等价于os.OpenFile(name.O_RDONLY,0)
//等同于这一句 (*╹▽╹*)
if err != nil {
fmt.Printf("err: %v\n", err)
} else {
fmt.Printf("f.Name(): %v\n", f.Name())
f.Close()
}
//第二种:
/**
读写文件、文件不存在就创建 权限755
*/
f2, err2 := os.OpenFile("a.txt", os.O_RDWR|os.O_CREATE, 0755)
if err2 != nil {
fmt.Printf("err2: %v\n", err2)
} else {
fmt.Printf("f2: %v\n", f2)
f.Close()
}
}
// 创建文件
func Create_file() {
//等价于,OpenFile(name,O_RDWR|O_CREATE|O_TRUNC,0666)
f, err := os.Create("a2.txt")
if err != nil {
fmt.Printf("err: %v\n", err)
}
fmt.Printf("f.Name(): %v\n", f.Name())
//第一个参数 目录默认,Temp 第二个参数 文件名前缀
f2, err2 := os.CreateTemp("", "temp")
if err2 != nil {
fmt.Printf("err2: %v\n", err2)
}
fmt.Printf("f2: %v\n", f2)
}
// 读 read
func readOps1() {
//循环读取
f, err := os.Open("a.txt")
if err != nil {
fmt.Printf("err: %v\n", err)
}
for {
//创建一个buffer缓冲区
buf := make([]byte, 10)
n, err2 := f.Read(buf)
fmt.Printf("n: %v\n", string(buf))
fmt.Printf("n: %v\n", n)
if err2 != nil && err == io.EOF {//end of file 文件末尾
fmt.Printf("err2: %v\n", err2)
break
}
}
f.Close()
}
func readOps2() {
f, err := os.Open("a.txt")
if err != nil {
fmt.Printf("err: %v\n", err)
}
buf := make([]byte, 10)
//从第5个字节开始读取10个字节
n, _ := f.ReadAt(buf, 5)
fmt.Printf("n: %v\n", n)
fmt.Printf("string(buf): %v\n", string(buf))
f.Close()
}
func readOps3() {
//测试a目录下面有b和c目录
f, _ := os.Open("a")
de, _ := f.ReadDir(-1)
for _, v := range de {
fmt.Printf("v.IsDir(): %v\n", v.IsDir())
fmt.Printf("v.Name(): %v\n", v.Name())
}
}
func readOps4() {
//定位
f, err := os.Open("a.txt")
if err != nil {
fmt.Printf("err: %v\n", err)
}
f.Seek(3, 0)
buf := make([]byte, 10)
n, _ := f.Read(buf)
fmt.Printf("n: %v\n", n)
fmt.Printf("string(buf): %v\n", string(buf))
f.Close()
}
使用缓冲区
...
func ReadByBufio(){
//打开文件
file,err:= os.Open("./xxx.txt")
if err!=nil{
fmt.printf("openfile is err,err:%v",err)
}
defer file.lose()
reader := bufio.NewReader(file)
line,err := reader.ReadString(`\n`)
if err!=nil{
fmt.Printf("read file by bufio file,err:%v",err)
return
}
}
循环
...
func ReadByBufio(){
//打开文件
file,err:= os.Open("./xxx.txt")
if err!=nil{
fmt.printf("openfile is err,err:%v",err)
}
defer file.lose()
reader := bufio.NewReader(file)
for{
line,err := reader.ReadString(`\n`)
if err!=nil&&err==io.EOF{
fmt.Printf("read file by bufio file,err:%v",err)
return
}
}
}
使用ioutil
//io/ioutil包的ReadFile方法能够读取完整的文件,只需要将文件名作为参数传入。
//注意:不能来读很大的文本,比如1G
package main
import (
"fmt"
"io/ioutil"
)
// ioutil.ReadFile读取整个文件
func main() {
content, err := ioutil.ReadFile("./main.go")
if err != nil {
fmt.Println("read file failed, err:", err)
return
}
fmt.Println(string(content))
}
File文件写操作
文件写入操作
os.OpenFile()函数能够以指定模式打开文件,从而实现文件写入相关功能。
func OpenFile(name string, flag int, perm FileMode) (*File, error) {
...
}
其中:
name:要打开的文件名 flag:打开文件的模式。 模式有以下几种:
| 模式 | 含义 |
|---|---|
os.O_WRONLY |
只写 |
os.O_CREATE |
创建文件 |
os.O_RDONLY |
只读 |
os.O_RDWR |
读写 |
os.O_TRUNC |
清空 |
os.O_APPEND |
追加 |
perm:文件权限,一个八进制数。r(读)04,w(写)02,x(执行)01。
//读写 --os.O_RDRW
//追加 ---os.O_APPEND 在原来的基础上追加
//覆盖 ----os.O_TRYNC 覆盖了原来的内容
这里结束和File结构体相关的文件写操作
os.OpenFile("a.txt", os.O_RDWR|os.O_APPEND, 0755)打开文件--->ff.Write([]byte("hello golang"))写---字节数组f.WriteString("hello word....")写--字符串f.WriteAt([]byte("aaa"), 3)写--随机写
package os
import "os"
//File 结构体写操作
//写字节数组
func Write() {
//读写 --os.O_RDRW
//追加 ---os.O_APPEND 在原来的基础上追加
//覆盖 ----os.O_TRYNC 覆盖了原来的内容
f, _ := os.OpenFile("a.txt", os.O_RDWR|os.O_APPEND, 0755)
f.Write([]byte("hello golang"))
f.Close()
}
//写字符串
func WriteString() {
f, _ := os.OpenFile("a.txt", os.O_RDWR|os.O_TRUNC, 0755)
f.WriteString("hello word....")
f.Close()
}
//随机写
func WriteAr() {
f, _ := os.OpenFile("a.txt", os.O_RDWR, 0755)
f.WriteAt([]byte("aaa"), 3)
f.Close()
}
bufio读取文件
bufio是在file的基础上封装了一层API,支持更多的功能。
package main
import (
"bufio"
"fmt"
"io"
"os"
)
// bufio按行读取示例
func main() {
file, err := os.Open("./xx.txt")
if err != nil {
fmt.Println("open file failed, err:", err)
return
}
defer file.Close()
reader := bufio.NewReader(file)
for {
line, err := reader.ReadString('\n') //注意是字符
if err == io.EOF {
if len(line) != 0 {
fmt.Println(line)
}
fmt.Println("文件读完了")
break
}
if err != nil {
fmt.Println("read file failed, err:", err)
return
}
fmt.Print(line)
}
}
ioutil读取整个文件
io/ioutil包的ReadFile方法能够读取完整的文件,只需要将文件名作为参数传入。
package main
import (
"fmt"
"io/ioutil"
)
// ioutil.ReadFile读取整个文件
func main() {
content, err := ioutil.ReadFile("./main.go")
if err != nil {
fmt.Println("read file failed, err:", err)
return
}
fmt.Println(string(content))
}
进程的相关操作
-
os.Getegid()当前正在运行进程的id -
os.Getppid()父进程id -
//设置新进程的属性 attr := &os.ProcAttr{ //file 指定新进程继承的活动文件对象 //前三个分别为,标准输入、标准输出、标准错误输出 Files: []*os.File{os.Stdin, os.Stdout, os.Stderr}, //新进程的环境变量 Env: os.Environ(), } //开始一个新的进程 p, err := os.StartProcess("C:\\Windows\\System32\\notepad.exe", []string{"C:\\Windows\\System32\\notepad.exe", "D:\\a.txt"}, attr) -
os.FindProcess(p.Pid)查找进程 -
p.Signal(os.Kill)向进程发出信号 -
p.Wait()等待进程p的退出,返回进程的状态
type Process struct{
Pid int
//contains filtered or unexported fields
}
例子:
package os
import (
"fmt"
"os"
"time"
)
//进程的相关操作
func Process_id() {
//获得当前正在运行的进程id
fmt.Printf("os.Getegid(): %v\n", os.Getegid())
//父进程id
fmt.Printf("os.Getppid(): %v\n", os.Getppid())
//设置新进程的属性
attr := &os.ProcAttr{
//file 指定新进程继承的活动文件对象
//前三个分别为,标准输入、标准输出、标准错误输出
Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},
//新进程的环境变量
Env: os.Environ(),
}
//开始一个新的进程
p, err := os.StartProcess("C:\\Windows\\System32\\notepad.exe", []string{"C:\\Windows\\System32\\notepad.exe", "D:\\a.txt"}, attr)
if err != nil {
fmt.Printf("err: %v\n", err)
}
fmt.Printf("p: %v\n", p)
fmt.Printf("p.Pid: %v\n", p.Pid)
//通过进程的ID查找进程
p2, _ := os.FindProcess(p.Pid)
fmt.Printf("p2: %v\n", p2)
//等待10秒,执行函数
time.AfterFunc(time.Second*10, func() {
//向p进程发送退出信号
p.Signal(os.Kill)
})
//等待进程p的退出,返回进程的状态
ps, _ := p.Wait()
fmt.Printf("ps.String(): %v\n", ps.String())
}
环境变量相关操作
os.Environ()获得所有的环境变量os.Getenv("GOPATH")获得某个环境变量 -key-valueos.Setenv("env1", "env1")设置某个环境变量os.LookupEnv("env1")查找一下环境变量os.Clearenv()清空环境变量
package os
import (
"fmt"
"os"
)
//环境相关的方法
func Environ() {
//获得所有的环境变量
s := os.Environ()
fmt.Printf("s: %v\n", s)
//获得某个环境变量--key-value
s2 := os.Getenv("GOPATH")
fmt.Printf("s2: %v\n", s2)
//设置环境变量
os.Setenv("env1", "env1")
s2 = os.Getenv("aaa")
fmt.Printf("s2: %v\n", s2)
fmt.Println("---------")
//查找--存在的话b--bool
s3, b := os.LookupEnv("env1")
fmt.Printf("b: %v\n", b)
fmt.Printf("s3: %v\n", s3)
//替换
os.Setenv("NAME", "gopher")
os.Setenv("BURROW", "/usr/gopher")
os.ExpandEnv("$NAME lives in ${BURROW}.")
//用NAME 和 BURROW的值替换语句中值
//清空环境变量
//os.Clearenv()
}
IO
GO语言中,为了方便开发者使用,将IO操作封装在了如下几个包中:
- io为IO原语(I/O primitives) 提供基本的接口 os File Reader Writer
- io/ioutil 封装了一些实用的I/O函数
- fmt 实现格式化I/O,类型C语言中的printf和scanf format fmt
- bufio 实现自带缓冲/O
io-基本的IO接口
在io包中最重要的两个接口:Reader和Writer接口。本章所提到的各种IO包都和这两个接口有关,也就是说只要实现了这两个接口,它就有了IO功能
Reader接口
type Reader interface {
Read(p []byte)(n int,err error)
}
Writer接口
type Writer interface{
Writer (p []byte)(n int,err error)
}
哪些类型实现了Reader和Writer接口
os.File 同时实现了 io.Reader 和 io.Writer
strings.Reader 实现了 io.Reader
bufio.Reader/Writer 分别实现了 io.Reader 和 io.writer
bytes.Buffer 实现了 io.Reader 和 io.Writer
bytes.Reader 实现了 io.Reader
compress/gzio.Reader/Writer 分别实现了 io.Reader和io.Writer
crypto/cipher.StreamReader/StreamWriter 分别实现了io.Reader和io.Writer
crypto/tls.Conn 同时实现了 io.Reader 和io.Writer
encoding/csv.Reader/Writer 分别实现了 io.Reader 和io.Writer
例子:
Time
time包
时间和日期是我们编程中经常会用到的,本文主要介绍了 Go 语言内置的 time 包的基本用法。time 包提供了一些关于时间显示和测量用的函数。time 包中日历的计算采用的是公历,不考虑润秒。
时间类型
Go 语言中使用time.Time类型表示时间。我们可以通过time.Now函数获取当前的时间对象,然后从时间对象中可以获取到年、月、日、时、分、秒等信息。
// timeDemo 时间对象的年月日时分秒
func timeDemo() {
now := time.Now() // 获取当前时间
fmt.Printf("current time:%v\n", now)
year := now.Year() // 年
month := now.Month() // 月
day := now.Day() // 日
hour := now.Hour() // 小时
minute := now.Minute() // 分钟
second := now.Second() // 秒
fmt.Println(year, month, day, hour, minute, second)
}
Location和time zone
Go 语言中使用 location 来映射具体的时区。时区(Time Zone)是根据世界各国家与地区不同的经度而划分的时间定义,全球共分为24个时区。中国差不多跨5个时区,但为了使用方便只用东八时区的标准时即北京时间为准。
下面的示例代码中使用beijing来表示东八区8小时的偏移量,其中time.FixedZone和time.LoadLocation`这两个函数则是用来获取location信息。
// timezoneDemo 时区示例
func timezoneDemo() {
// 中国没有夏令时,使用一个固定的8小时的UTC时差。
// 对于很多其他国家需要考虑夏令时。
secondsEastOfUTC := int((8 * time.Hour).Seconds())
// FixedZone 返回始终使用给定区域名称和偏移量(UTC 以东秒)的 Location。
beijing := time.FixedZone("Beijing Time", secondsEastOfUTC)
// 如果当前系统有时区数据库,则可以加载一个位置得到对应的时区
// 例如,加载纽约所在的时区
newYork, err := time.LoadLocation("America/New_York") // UTC-05:00
if err != nil {
fmt.Println("load America/New_York location failed", err)
return
}
fmt.Println()
// 加载上海所在的时区
//shanghai, err := time.LoadLocation("Asia/Shanghai") // UTC+08:00
// 加载东京所在的时区
//tokyo, err := time.LoadLocation("Asia/Tokyo") // UTC+09:00
// 创建时间对象需要指定位置。常用的位置是 time.Local(当地时间) 和 time.UTC(UTC时间)。
//timeInLocal := time.Date(2009, 1, 1, 20, 0, 0, 0, time.Local) // 系统本地时间
timeInUTC := time.Date(2009, 1, 1, 12, 0, 0, 0, time.UTC)
sameTimeInBeijing := time.Date(2009, 1, 1, 20, 0, 0, 0, beijing)
sameTimeInNewYork := time.Date(2009, 1, 1, 7, 0, 0, 0, newYork)
// 北京时间(东八区)比UTC早8小时,所以上面两个时间看似差了8小时,但表示的是同一个时间
timesAreEqual := timeInUTC.Equal(sameTimeInBeijing)
fmt.Println(timesAreEqual)
// 纽约(西五区)比UTC晚5小时,所以上面两个时间看似差了5小时,但表示的是同一个时间
timesAreEqual = timeInUTC.Equal(sameTimeInNewYork)
fmt.Println(timesAreEqual)
}
在日常编码过程中使用时间对象的时候一定要注意其时区信息。
Unix Time
Unix Time是自1970年1月1日 00:00:00 UTC 至当前时间经过的总秒数。下面的代码片段演示了如何基于时间对象获取到Unix 时间。
// timestampDemo 时间戳
func timestampDemo() {
now := time.Now() // 获取当前时间
timestamp := now.Unix() // 秒级时间戳
milli := now.UnixMilli() // 毫秒时间戳 Go1.17+
micro := now.UnixMicro() // 微秒时间戳 Go1.17+
nano := now.UnixNano() // 纳秒时间戳
fmt.Println(timestamp, milli, micro, nano)
}
time 包还提供了一系列将 int64 类型的时间戳转换为时间对象的方法。
// timestamp2Time 将时间戳转为时间对象
func timestamp2Time() {
// 获取北京时间所在的东八区时区对象
secondsEastOfUTC := int((8 * time.Hour).Seconds())
beijing := time.FixedZone("Beijing Time", secondsEastOfUTC)
// 北京时间 2022-02-22 22:22:22.000000022 +0800 CST
t := time.Date(2022, 02, 22, 22, 22, 22, 22, beijing)
var (
sec = t.Unix()
msec = t.UnixMilli()
usec = t.UnixMicro()
)
// 将秒级时间戳转为时间对象(第二个参数为不足1秒的纳秒数)
timeObj := time.Unix(sec, 22)
fmt.Println(timeObj) // 2022-02-22 22:22:22.000000022 +0800 CST
timeObj = time.UnixMilli(msec) // 毫秒级时间戳转为时间对象
fmt.Println(timeObj) // 2022-02-22 22:22:22 +0800 CST
timeObj = time.UnixMicro(usec) // 微秒级时间戳转为时间对象
fmt.Println(timeObj) // 2022-02-22 22:22:22 +0800 CST
}
时间间隔
time.Duration是time包定义的一个类型,它代表两个时间点之间经过的时间,以纳秒为单位。time.Duration表示一段时间间隔,可表示的最长时间段大约290年。
time 包中定义的时间间隔类型的常量如下:
const (
Nanosecond Duration = 1
Microsecond = 1000 * Nanosecond
Millisecond = 1000 * Microsecond
Second = 1000 * Millisecond
Minute = 60 * Second
Hour = 60 * Minute
)
例如:time.Duration表示1纳秒,time.Second表示1秒。
时间操作
Add
Go语言的时间对象有提供Add方法如下:
func (t Time) Add(d Duration) Time
举个例子,求一个小时之后的时间:
func main() {
now := time.Now()
later := now.Add(time.Hour) // 当前时间加1小时后的时间
fmt.Println(later)
}
Sub
求两个时间之间的差值:
func (t Time) Sub(u Time) Duration
返回一个时间段t-u。如果结果超出了Duration可以表示的最大值/最小值,将返回最大值/最小值。要获取时间点t-d(d为Duration),可以使用t.Add(-d)。
Equal
func (t Time) Equal(u Time) bool
判断两个时间是否相同,会考虑时区的影响,因此不同时区标准的时间也可以正确比较。本方法和用t==u不同,这种方法还会比较地点和时区信息。
Before
func (t Time) Before(u Time) bool
如果t代表的时间点在u之前,返回真;否则返回假。
After
func (t Time) After(u Time) bool
如果t代表的时间点在u之后,返回真;否则返回假。
定时器
使用time.Tick(时间间隔)来设置定时器,定时器的本质上是一个通道(channel)。
func tickDemo() {
ticker := time.Tick(time.Second) //定义一个1秒间隔的定时器
for i := range ticker {
fmt.Println(i)//每秒都会执行的任务
}
}
时间格式化
time.Format函数能够将一个时间对象格式化输出为指定布局的文本表示形式,需要注意的是 Go 语言中时间格式化的布局不是常见的Y-m-d H:M:S,而是使用 2006-01-02 15:04:05.000(记忆口诀为2006 1 2 3 4 5)。
其中:
- 2006:年(Y)
- 01:月(m)
- 02:日(d)
- 15:时(H)
- 04:分(M)
- 05:秒(S)
补充
- 如果想格式化为12小时格式,需在格式化布局中添加
PM。 - 小数部分想保留指定位数就写0,如果想省略末尾可能的0就写 9。
// formatDemo 时间格式化
func formatDemo() {
now := time.Now()
// 格式化的模板为 2006-01-02 15:04:05
// 24小时制
fmt.Println(now.Format("2006-01-02 15:04:05.000 Mon Jan"))
// 12小时制
fmt.Println(now.Format("2006-01-02 03:04:05.000 PM Mon Jan"))
// 小数点后写0,因为有3个0所以格式化输出的结果也保留3位小数
fmt.Println(now.Format("2006/01/02 15:04:05.000")) // 2022/02/27 00:10:42.960
// 小数点后写9,会省略末尾可能出现的0
fmt.Println(now.Format("2006/01/02 15:04:05.999")) // 2022/02/27 00:10:42.96
// 只格式化时分秒部分
fmt.Println(now.Format("15:04:05"))
// 只格式化日期部分
fmt.Println(now.Format("2006.01.02"))
}
解析字符串格式的时间
对于从文本的时间表示中解析出时间对象,time包中提供了time.Parse和time.ParseInLocation两个函数。
其中time.Parse在解析时不需要额外指定时区信息。
// parseDemo 指定时区解析时间
func parseDemo() {
// 在没有时区指示符的情况下,time.Parse 返回UTC时间
timeObj, err := time.Parse("2006/01/02 15:04:05", "2022/10/05 11:25:20")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(timeObj) // 2022-10-05 11:25:20 +0000 UTC
// 在有时区指示符的情况下,time.Parse 返回对应时区的时间表示
// RFC3339 = "2006-01-02T15:04:05Z07:00"
timeObj, err = time.Parse(time.RFC3339, "2022-10-05T11:25:20+08:00")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(timeObj) // 2022-10-05 11:25:20 +0800 CST
}
time.ParseInLocation函数需要在解析时额外指定时区信息。
// parseDemo 解析时间
func parseDemo() {
now := time.Now()
fmt.Println(now)
// 加载时区
loc, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
fmt.Println(err)
return
}
// 按照指定时区和指定格式解析字符串时间
timeObj, err := time.ParseInLocation("2006/01/02 15:04:05", "2022/10/05 11:25:20", loc)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(timeObj)
fmt.Println(timeObj.Sub(now))
}
浙公网安备 33010602011771号