【问题标题】:Problem parsing values of PostgreSQL TIMESTAMP type解析 PostgreSQL TIMESTAMP 类型值的问题
【发布时间】:2019-03-28 20:26:43
【问题描述】:

在 PostgreSQL 中,我有一个名为 surveys 的表。

CREATE TABLE SURVEYS(
  SURVEY_ID UUID PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
  SURVEY_NAME VARCHAR NOT NULL,
  SURVEY_DESCRIPTION TEXT,
  START_PERIOD TIMESTAMP,
  END_PERIOD TIMESTAMP
);

如您所见,只有 SURVEY_IDSURVEY_NAME 列是 NOT NULL

在 Go 中,我想通过 POST 请求在该表中创建新条目。我这样发送 JSON 对象:

{
    "survey_name": "NAME",
    "survey_description": "DESCRIPTION",
    "start_period": "2019-01-01 00:00:00",
    "end_period": "2019-02-28 23:59:59"
}

不幸的是,它引发了奇怪的ERROR

parsing time ""2019-01-01 00:00:00"" as ""2006-01-02T15:04:05Z07:00"": cannot parse " 00:00:00"" as "T"

我在哪里出错以及如何解决我的问题?

models/surveys.go:

import (
    "database/sql"
    "time"
)

type NullTime struct {
    time.Time
    Valid bool
}

type Survey struct {
    ID int `json:"survey_id"`
    Name string `json:"survey_name"`
    Description sql.NullString `json:"survey_description"`
    StartPeriod NullTime `json:"start_period"`
    EndPeriod NullTime `json:"end_period"`
}

controllers/surveys.go:

var CreateSurvey = func(responseWriter http.ResponseWriter, request *http.Request) {
    // Initialize variables.
    survey := models.Survey{}
    var err error

    // The decoder introduces its own buffering and may read data from argument beyond the JSON values requested.
    err = json.NewDecoder(request.Body).Decode(&survey)
    if err != nil {
        log.Println(err)
        utils.ResponseWithError(responseWriter, http.StatusInternalServerError, err.Error())
        return
    }
    defer request.Body.Close()

    // Execute INSERT SQL statement.
    _, err = database.DB.Exec("INSERT INTO surveys (survey_name, survey_description, start_period, end_period) VALUES ($1, $2, $3, $4);", survey.Name, survey.Description, survey.StartPeriod, survey.EndPeriod)

    // Shape the response depending on the result of the previous command.
    if err != nil {
        log.Println(err)
        utils.ResponseWithError(responseWriter, http.StatusInternalServerError, err.Error())
        return
    }
    utils.ResponseWithSuccess(responseWriter, http.StatusCreated, "The new entry successfully created.")
}

【问题讨论】:

  • time.Time's UnmarshalJSON 使用RFC3339 格式来解析时间戳字符串,因此您需要相应地更改您在json中发送的时间戳的格式,或者您需要实现自己的@ 987654333@ 在您的 NullTime 类型上。

标签: go


【解决方案1】:

错误已经说明出了什么问题:

将时间“2019-01-01 00:00:00”解析为“2006-01-02T15:04:05Z07:00”:无法将“00:00:00”解析为“T”

您正在传递"2019-01-01 00:00:00",而它需要不同的时间格式,即RFC3339 (UnmarshalJSON's default)。

要解决这个问题,您要么希望以预期的格式"2019-01-01T00:00:00Z00:00" 传递时间,要么像这样定义自己的类型CustomTime

const timeFormat = "2006-01-02 15:04:05"

type CustomTime time.Time

func (ct *CustomTime) UnmarshalJSON(data []byte) error {
    newTime, err := time.Parse(timeFormat, strings.Trim(string(data), "\""))
    if err != nil {
        return err
    }

    *ct = CustomTime(newTime)
    return nil
}

func (ct *CustomTime) MarshalJSON() ([]byte, error) {
    return []byte(fmt.Sprintf("%q", time.Time(*ct).Format(timeFormat))), nil
}

注意,您可能还需要实现ValuerScanner 接口,以便在数据库中进行解析,如下所示:

func (ct CustomTime) Value() (driver.Value, error) {
    return time.Time(ct), nil
}

func (ct *CustomTime) Scan(src interface{}) error {
    if val, ok := src.(time.Time); ok {
        *ct = CustomTime(val)
    } else {
        return errors.New("time Scanner passed a non-time object")
    }

    return nil
}

Go Playground 示例。

【讨论】:

  • @NurzhanNogerbek 要能够保存/检索像 NullTime 这样的自定义结构类型,您需要让它们实现 ScannerValuer 接口。例如,只需查看database/sql 文档,您会发现多个自定义NullXxx 结构类型,包括NullString,它们都实现了这两个接口。您也需要这样做才能修复 sql: unsupported type 错误。
  • @NurzhanNogerbek “为什么timeFormat变量的值是2006-01-02 15:04:05?”我们称之为“参考时间”。 time 包使用参考时间来解析布局值,然后将基于该参考时间将 Parse/Format time.Time 值。如果您转到documentation 并搜索 (CTRL+F) 以查找“参考时间”,您将找到足够的信息来帮助您更好地理解它。
  • @NurzhanNogerbek 这取决于您如何实现 Value() (driver.Value, error) 方法。
  • @NurzhanNogerbek 例如,看看NullString 是如何做到的,如果它无效,它会返回nil,它会在数据库中保存为NULL。所以你必须检查你的CustomTime是否有效,如果不是返回nil,是否返回你现在返回的内容。
  • @NurzhanNogerbek Valid 不是 Abdullah 建议的 CustomTime 的一部分,它是在您的原始类型 NullTime 上声明的字段,我建议您坚持这种方法。使用 abdulla 的所有建议,但使用 NullTime 代替 CustomTime,即在 NullTime 上实现这些方法并完全放弃 CustomTime。澄清一下,这里命名不是问题,类型的结构是,所以如果你想调用它 CustomTime 继续,只需将其更改为正确的结构。示例:play.golang.com/p/JU7xUuQTCO1
猜你喜欢
  • 2011-10-01
  • 2012-03-31
  • 2022-11-11
  • 1970-01-01
  • 1970-01-01
  • 2021-05-28
  • 2021-11-11
  • 1970-01-01
  • 2022-01-06
相关资源
最近更新 更多