Golang如何仅显示日期而不显示时间

Golang如何仅显示日期而不显示时间 我正在寻找用SQL值填充Go模板的方法。目前我有三种方案,各有优缺点。然而,日期显示方式有些奇怪。

在PGAdmin中,日期正确显示为 2020-08-26 使用标准sql库,日期显示为 2020-08-26 00:00:00 +0000 +0000 使用sqlx,日期显示为 2020-08-26T00:00:00Z 使用json,日期正确显示为 2020-08-26

或者,Go结构体中是否没有日期格式?

1. 标准SQL库 标准sql库是惯用的方法,但你必须重复定义结构体两次。

Go Playground - The Go Programming Language

http://94.237.92.101:5051/

2. Sqlx Sqlx是标准库的扩展,提供了"StructScan"功能,使其更加通用。我认为这仍然是一种惯用的方式。

Go Playground - The Go Programming Language

http://94.237.92.101:5052/

3. JSON实验 这是最高级别的DRY(不要重复自己),但我认为这是一个死胡同。因为json_agg()函数无法与连接查询等一起工作。不过,在某些情况下我仍然认为它是一个选项。

Go Playground - The Go Programming Language

http://94.237.92.101:5053/

我的问题

  1. 如何去除方案1和2中的时间部分(“T00:00:00Z”)?有没有办法在结构体中使用类似time.Date(YYYY-MM-DD)的方式?
  2. 是否有更多方法来实现这个目标?更简单或更好的方法?

更多关于Golang如何仅显示日期而不显示时间的实战教程也可以访问 https://www.itying.com/category-94-b0.html

18 回复

skillian:

我不确定这对你是否有帮助

我不知道如何将 SQL查询 格式化为不带时间戳的ISO标准。

更多关于Golang如何仅显示日期而不显示时间的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Sibert:

在我看来,Go 语言缺少一个标准的日期库。

Google Cloud Go 日期包:Package civil

我不确定这对你是否有帮助,但这里我将演示如何拥有一个 time.Time 结构体字段,但在模板中我希望使用特定的格式:https://play.golang.org/p/LqCTJ41xLr_1

func main() {
    fmt.Println("hello world")
}

petrus:

代码可以编译,但我无法测试它。

谢谢!它按预期工作了。我自己永远也搞不明白这个。字符串 + 时间 🙂

// 此处假设原始HTML的<div class="post">...</div>中可能包含代码,但提供的HTML片段中没有显式的代码块。
// 根据指令,如果后续内容中有Go代码,应放置在此代码块内。

为什么你要求SQL查询不包含时间戳?我在这里修改了我的示例,在GetModel函数中包含了小时、分钟和秒,以展示如何将记录加载到使用sqlxdatabase/sql的切片中,但由于我正在格式化输出,显示时并不包含那个时间。如果你试图用SQL来实现,你可以在SQL中转换数据类型。如果你使用的是MS SQL Server,你可以使用cast(hr_date as date)convert(date, hr_date)等。

我从你在 play.golang.org 上的示例中看到了如何从 SQL 中获取数据,但你是如何准备数据以供查看的呢?你是让默认的 time.Time.String 函数来格式化它吗?我想你需要的可能是 time.Time.Format,它允许你指定想要的精确格式(例如 hr := get().([]Hr)[0]; hr.Date.Format("2006-01-02");)。

hr := get().([]Hr)[0]; hr.Date.Format("2006-01-02");

petrus:

Google Cloud Go 日期包:Package civil

但我该如何使用它呢?文档中没有结构体的示例。

type Hr struct {
	Date civil.Date `db:"hr_date"`
	Sign string     `db:"hr_sign"`
	Code string     `db:"hr_code"`
	Qty  float64    `db:"hr_qty"`
	Sum  float64    `db:"hr_sum"`
}

错误:*扫描第 0 列索引时出错,列名 “hr_date”:不支持扫描,将 driver.Value 类型 time.Time 存储到类型 civil.Date

skillian:

time.Time.Format 允许你指定想要的精确格式(例如 hr := get().([]Hr)[0]; hr.Date.Format("2006-01-02");)。

我尝试了以下方法(使用sqlx):

list := []Hr{}
	for rows.Next() {
		row := Hr{}
		hr := get().([]Hr)[0]
		hr.Date.Format("2006-01-02")
		err := rows.StructScan(&row)
		if err != nil {
			log(err.Error())
		}
		list = append(list, row)
	}

但是出现了一个错误:hr.Date.Format undefined (type string has no field or method Format)

skillian:

你是如何使用这些 Hr 结构的?

说实话,我也不知道自己在做什么。我多年来学到的所有知识在 Go 中都不适用。对我来说,结构体是对表中列类型的描述,至少应具备基本的格式化能力,比如短日期或日期+时间格式。有一个库

GitHub - rickb777/date: 一个用于处理日期的 Go 包

看起来能实现我想要的功能,但我还没能让它正常工作。而且我不知道它是否能作为结构体的列类型使用。

在我看来,Go 缺少一个标准日期库,以便在结构体中使用。

skillian:

那么当你想要显示它时,问题就出现了

我认为 Go 不应该改变来自数据库的值。数据库已经将数据转换为正确的格式。为什么 Go 还要改变这些值呢?

你是如何使用这些 Hr 结构的?它们仅用于显示目的吗(例如,它们只是你网站上该表格的模型)?如果是这样,那么你可以为 Date 字段使用 string 类型。如果你有任何使用该 Date 字段的代码(例如,按特定日期筛选),那么你可能应该使用 time.Time 类型。

如果你使用 time.Time 类型,那么你就不需要解析时间;(*sqlx.Rows).StructScan 会处理将值读取为“时间类型”(例如 datedatetimedatetime2,无论 RDBMS 返回什么)。这样一来,你的问题就变成了:当你想要显示它时,如果你不想要从 time.Time.String 得到的默认长格式输出,你就必须调用 time.Time.Format 并提供一个格式字符串。

skillian:

但是你是如何准备它以供查看的呢?

type Hr struct {
	Date time.Time
	Sign string
	Code string
	Qty  float64
	Sum  float64
}

我应该如何准备?我认为通过在结构体中设置类型来准备它是合乎逻辑的。还是我想错了?

“结构体”的目的是什么?难道不是用来设置列的类型吗?Go 中没有“日期”列吗?

skillian:

我想你正在寻找 time.Time.Format,它允许你指定你想要的精确格式(例如 hr := get().([]Hr)[0]; hr.Date.Format("2006-01-02");)。

那么我如何以及何时在查询中实现 time.Time.Format?是在查询中,还是在循环 for rows.Next() 时?我如何用 sqlx 实现这个?还是我应该把它放在结构体里?

尝试使用一个辅助数据结构 HrDB

type HrDB struct {
	Date time.Time  `db:"hr_date"`
	Hr
}

type Hr struct {
	Date string
	Sign string  `db:"hr_sign"`
	Code string  `db:"hr_code"`
	Qty  float64 `db:"hr_qty"`
	Sum  float64 `db:"hr_sum"`
}

有没有办法让这个结构更“通用”一些?例如,在 Hr 结构体下创建一个自定义的“ISO”类型?我知道“泛型”在 Go 里是个敏感词,但我只是好奇。伪代码如下:

type ISO func () {
	Date.Format("2006-01-02")
}

type Hr struct {
	Date *ISO    `db:"hr_date"` // 通用的自定义类型
	Sign string  `db:"hr_sign"`
	Code string  `db:"hr_code"`
	Qty  float64 `db:"hr_qty"`
	Sum  float64 `db:"hr_sum"`
}

是的,这个方法有效。谢谢!

但是如果结构体字段是 time.Time 类型,我该如何格式化呢?下面的代码不起作用。

type Hr struct {
	Date time.Time `db:"hr_date"`
	Sign string    `db:"hr_sign"`
	Code string    `db:"hr_code"`
	Qty  float64   `db:"hr_qty"`
	Sum  float64   `db:"hr_sum"`
}

func get() interface{} {
	rows, err := db.Queryx("SELECT *::text FROM hr")
	if err != nil {
		log("getsql error" + err.Error())
	}
	defer rows.Close()

	list := []Hr{}
	for rows.Next() {
		row := Hr{}
		err := rows.StructScan(&row)
		if err != nil {
			log(err.Error())
		}
		time.Parse("2006-01-02", row.Date)
		list = append(list, row)
	}

	return (list)
}

错误信息:cannot use row.Date (type time.Time) as type string in argument to time.Parse

谷歌云 Go 日期包:Package civil

但是我该如何使用它呢?

type HrDB struct {
	Date time.Time `db:"hr_date"`
	Hr
}

type Hr struct {
	Date civil.Date
	Sign string  `db:"hr_sign"`
	Code string  `db:"hr_code"`
	Qty  float64 `db:"hr_qty"`
	Sum  float64 `db:"hr_sum"`
}
list := []Hr{}
for rows.Next() {
	row := HrDB{}
	err := rows.StructScan(&row)
	if err != nil {
		log(err.Error())
	}
	row.Hr.Date = civil.DateOf(row.Date)
	list = append(list, row.Hr)
}
return (list)

Go Playground - The Go Programming Language

要从 Hr.Date 字段获取 ISO 格式的日期 (2020-08-31),请使用 String() 方法。例如:

isoDate = row.Hr.Date.String()

Sibert:

我尝试了以下代码(使用sqlx):

list := []Hr{}
	for rows.Next() {
		row := Hr{}
		hr := get().([]Hr)[0]
		hr.Date.Format("2006-01-02")
		err := rows.StructScan(&row)
		if err != nil {
			log(err.Error())
		}
		list = append(list, row)
	}

但出现错误:hr.Date.Format undefined (type string has no field or method Format)

在你的sqlx示例中,Hr.Date 是一个 string 类型。

Sibert:

Go Playground - The Go Programming Language http://94.237.92.101:5052/

type Hr struct {
	Date string `db:"hr_date"`
	Sign string `db:"hr_sign"`
	Code string `db:"hr_code"`
	Qty float64 `db:"hr_qty"`
	Sum float64 `db:"hr_sum"`
}

list := []Hr{}
for rows.Next() {
	row := Hr{}
	err := rows.StructScan(&row)
	if err != nil {
		log(err.Error())
		continue
	}
	if len(row.Date) >= 10 {
		row.Date = row.Date[:10]
	}
	list = append(list, row)
}
return (list)

Go Playground - The Go Programming Language Go Playground - The Go Programming Language

如何格式化 time.Time 类型的结构体?这个方法不起作用。

type Hr struct {
	Date time.Time `db:"hr_date"`
	Sign string    `db:"hr_sign"`
	Code string    `db:"hr_code"`
	Qty  float64   `db:"hr_qty"`
	Sum  float64   `db:"hr_sum"`
}

func get() interface{} {
	rows, err := db.Queryx("SELECT *::text FROM hr")
	if err != nil {
		log("getsql error" + err.Error())
	}
	defer rows.Close()

	list := []Hr{}
	for rows.Next() {
		row := Hr{}
		err := rows.StructScan(&row)
		if err != nil {
			log(err.Error())
		}
		time.Parse("2006-01-02", row.Date)
		list = append(list, row)
	}

	return (list)
}

错误:无法在 time.Parse 的参数中将 row.Date(类型为 time.Time)用作字符串类型

尝试使用一个辅助数据结构 HrDB

type HrDB struct {
	Date time.Time `db:"hr_date"`
	Hr
}

type Hr struct {
	Date string
	Sign string  `db:"hr_sign"`
	Code string  `db:"hr_code"`
	Qty  float64 `db:"hr_qty"`
	Sum  float64 `db:"hr_sum"`
}
list := []Hr{}
for rows.Next() {
	row := HrDB{}
	err := rows.StructScan(&row)
	if err != nil {
		log(err.Error())
	}
	row.Hr.Date = row.Date.Format("2006-01-02")
	list = append(list, row.Hr)
}
return (list)

Go Playground - The Go Programming Language

代码可以编译,但我无法测试它。

为什么我想要不带时区的“2006-01-02”? 正如有人指出的那样——出生日期不带毫秒看起来更美观。所以这主要是一个显示问题。由于PostgreSQL的输出是“2006-01-02”,我希望保持这种格式,而不给出生日期添加毫秒。根据维基百科,唯一使用MM/DD/YYYY系统的国家是美国、菲律宾、帕劳、加拿大和密克罗尼西亚。因此,ISO 8601是一个应该被支持的全球标准。

我的新手结论: 无论对错。Go语言无法一步到位地支持ISO 8601短日期格式。这个结论基于以下来源得出:

https://golang.org/search?q=iso+8601 https://godoc.org/?q=iso+8601

time包应该有一些ISO 8601日期和日期/时间格式

我认为time包应该有一些ISO 8601日期和日期/时间格式,例如: ISO8601Date = “2006-01-02” ISO8601DateTime = “2006-01-02T15:04:05Z”

https://golang.org/search?q=“2006-01-02”

ISO 8601可以通过几种变通方法得到支持。但我的经验是,每一行额外的代码都会消耗性能并增加执行时间。或者使其更难阅读和维护。

那么RFC3339呢? RFC3339应该与ISO 8601相同(我认为),但在结构体中仍然无法使用它

type Hr struct {
	Date time.RFC3339 `db:"hr_date"`
	Sign string       `db:"hr_sign"`
	Code string       `db:"hr_code"`
	Qty  float64      `db:"hr_qty"`
	Sum  float64      `db:"hr_sum"`
}

错误:time.RFC3339 不是一个类型

变通方法1:双重结构体 这是一个克服缺乏ISO支持的绝妙方法。

type HrDB struct {
	Date time.Time `db:"hr_date"`
	Hr
}

type Hr struct {
	Date string
	Sign string  `db:"hr_sign"`
	Code string  `db:"hr_code"`
	Qty  float64 `db:"hr_qty"`
	Sum  float64 `db:"hr_sum"`
}

然而,这并不能消除对所有涉及日期的解析。你必须专门处理每一个涉及的日期列。

time.Parse("2006-01-02", row.Date) time.Parse("2006-01-02", row.Edit) 等等

变通方法2:使用字符串 这可能是处理数百个结构体的最简单方法。但仍然不简单。

type Hr struct {
	Date string `db:"hr_date"`
	Sign string `db:"hr_sign"`
	Code string `db:"hr_code"`
	Qty float64 `db:"hr_qty"`
	Sum float64 `db:"hr_sum"`
}

list := []Hr{}
for rows.Next() {
	row := Hr{}
	err := rows.StructScan(&row)
	if err != nil {
		log(err.Error())
		continue
	}
    row.Date = row.Date[:10]
	list = append(list, row)
}
return (list)

变通方法3:在PostgreSQL中转换 在PostgreSQL中转换更简单,但有局限性:

"SELECT hr_date::text, hr_sign, hr_code, hr_qty, hr_sum FROM hr"

转换所有列将不起作用

"SELECT *::text FROM hr"

我在寻找什么? 我正在寻找一种方法,可以让我使用结构体作为具有有限格式化能力的“列类型”。即一步到位的解决方案。我见过许多非标准库声称应该以这种方式工作,但我没有一个能让它们工作起来。

type Hr struct {
	Date date.ISO8601 `db:"hr_date"`
	Sign string       `db:"hr_sign"`
	Code string       `db:"hr_code"`
	Qty float64       `db:"hr_qty"`
	Sum float64       `db:"hr_sum"`
}

我感谢所有收到的有用回答,并且我发现来自petrus的“字符串解决方案”是最接近且最简单的。尽管这不是一个新手所设想的方式

在Go中处理日期格式时,确实需要手动处理时间部分的格式化。以下是针对你问题的解决方案:

1. 自定义类型处理日期

import (
    "database/sql"
    "encoding/json"
    "time"
)

// 自定义日期类型
type Date struct {
    time.Time
}

// 实现sql.Scanner接口
func (d *Date) Scan(value interface{}) error {
    if value == nil {
        d.Time = time.Time{}
        return nil
    }
    
    switch v := value.(type) {
    case time.Time:
        d.Time = v
    case []byte:
        t, err := time.Parse("2006-01-02", string(v))
        if err != nil {
            return err
        }
        d.Time = t
    case string:
        t, err := time.Parse("2006-01-02", v)
        if err != nil {
            return err
        }
        d.Time = t
    default:
        return fmt.Errorf("unsupported type: %T", v)
    }
    return nil
}

// 实现driver.Valuer接口
func (d Date) Value() (driver.Value, error) {
    if d.IsZero() {
        return nil, nil
    }
    return d.Format("2006-01-02"), nil
}

// 自定义JSON序列化
func (d Date) MarshalJSON() ([]byte, error) {
    if d.IsZero() {
        return []byte("null"), nil
    }
    return []byte(`"` + d.Format("2006-01-02") + `"`), nil
}

// 自定义JSON反序列化
func (d *Date) UnmarshalJSON(data []byte) error {
    if string(data) == "null" {
        d.Time = time.Time{}
        return nil
    }
    
    str := string(data)
    if len(str) > 2 && str[0] == '"' && str[len(str)-1] == '"' {
        str = str[1 : len(str)-1]
    }
    
    t, err := time.Parse("2006-01-02", str)
    if err != nil {
        return err
    }
    d.Time = t
    return nil
}

// 在结构体中使用
type User struct {
    ID        int    `db:"id"`
    Name      string `db:"name"`
    BirthDate Date   `db:"birth_date"`
}

2. 使用模板函数格式化日期

import (
    "html/template"
    "time"
)

// 创建模板函数
func formatDate(t time.Time) string {
    return t.Format("2006-01-02")
}

// 在模板中使用
tmpl := template.New("").Funcs(template.FuncMap{
    "formatDate": formatDate,
})

// 模板内容
const tmplStr = `
{{range .Users}}
    <div>
        <h3>{{.Name}}</h3>
        <p>出生日期: {{formatDate .BirthDate}}</p>
    </div>
{{end}}
`

3. 使用sqlx的StructScan配合自定义类型

import (
    "github.com/jmoiron/sqlx"
    _ "github.com/lib/pq"
)

type User struct {
    ID        int    `db:"id"`
    Name      string `db:"name"`
    BirthDate Date   `db:"birth_date"`
}

func getUsers(db *sqlx.DB) ([]User, error) {
    var users []User
    err := db.Select(&users, "SELECT id, name, birth_date FROM users")
    if err != nil {
        return nil, err
    }
    return users, nil
}

4. 使用标准库的格式化方法

// 在需要显示的地方直接格式化
func displayUser(user User) {
    fmt.Printf("姓名: %s\n", user.Name)
    fmt.Printf("日期: %s\n", user.BirthDate.Format("2006-01-02"))
}

// 或者在模板中直接使用Format方法
// {{.BirthDate.Format "2006-01-02"}}

5. 使用第三方库(如gorm)

import (
    "gorm.io/driver/postgres"
    "gorm.io/gorm"
    "gorm.io/gorm/schema"
)

// 自定义GORM数据类型
type DateType struct {
    time.Time
}

func (d *DateType) Scan(value interface{}) error {
    // 实现扫描逻辑
}

func (d DateType) Value() (driver.Value, error) {
    // 实现值转换逻辑
}

// 在GORM模型中使用
type User struct {
    gorm.Model
    Name      string
    BirthDate DateType
}

示例:完整的数据库操作

package main

import (
    "database/sql"
    "fmt"
    "log"
    
    _ "github.com/lib/pq"
    "github.com/jmoiron/sqlx"
)

// 使用自定义Date类型
type User struct {
    ID        int    `db:"id"`
    Name      string `db:"name"`
    BirthDate Date   `db:"birth_date"`
}

func main() {
    // 连接数据库
    db, err := sqlx.Connect("postgres", "user=postgres dbname=test sslmode=disable")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()
    
    // 查询数据
    var users []User
    err = db.Select(&users, "SELECT id, name, birth_date FROM users")
    if err != nil {
        log.Fatal(err)
    }
    
    // 显示结果(只显示日期)
    for _, user := range users {
        fmt.Printf("ID: %d, Name: %s, BirthDate: %s\n", 
            user.ID, 
            user.Name, 
            user.BirthDate.Format("2006-01-02"))
    }
}

这些方法都可以让你在Go中只显示日期而不显示时间。自定义类型方法是最彻底的解决方案,它确保在整个应用程序中日期都以统一的格式处理。

回到顶部