From 2c303cfba72f397116d8c3c05fc01f3b08d80eca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Fri, 16 Jun 2023 08:25:01 +0800 Subject: [PATCH] =?UTF-8?q?feat(types):=E5=A2=9E=E5=8A=A0=E6=97=A5?= =?UTF-8?q?=E6=9C=9F=E6=97=B6=E9=97=B4=E7=9A=84=E8=8C=83=E5=9B=B4=E5=8C=BA?= =?UTF-8?q?=E9=97=B4=E7=B1=BB=E5=9E=8B=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- types/date.go | 9 ++++ types/daterange.go | 80 ++++++++++++++++++++++++++++ types/datetime.go | 21 ++++++++ types/datetimerange.go | 80 ++++++++++++++++++++++++++++ types/range.go | 115 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 305 insertions(+) create mode 100644 types/daterange.go create mode 100644 types/datetimerange.go create mode 100644 types/range.go diff --git a/types/date.go b/types/date.go index 4897776..188b089 100644 --- a/types/date.go +++ b/types/date.go @@ -153,6 +153,15 @@ func (d Date) IsEmpty() bool { return d.Time.IsZero() } +func (d *Date) Parse(s string) error { + t, err := ParseDate(s) + if err != nil { + return err + } + d.Time = t.Time + return nil +} + func (d Date) ToString() string { return d.Time.Format("2006-01-02") } diff --git a/types/daterange.go b/types/daterange.go new file mode 100644 index 0000000..17308b1 --- /dev/null +++ b/types/daterange.go @@ -0,0 +1,80 @@ +package types + +import ( + "database/sql" + "database/sql/driver" + "electricity_bill_calc/tools" + "encoding/json" + "errors" + + "github.com/jackc/pgx/v5/pgtype" +) + +type DateRange struct { + pgtype.Range[Date] +} + +func NewDateRange(lower *Date, upper *Date) DateRange { + return DateRange{ + Range: pgtype.Range[Date]{ + LowerType: tools.Cond(lower != nil, pgtype.Inclusive, pgtype.Unbounded), + Lower: tools.DefaultTo(lower, MinDate()), + UpperType: tools.Cond(upper != nil, pgtype.Inclusive, pgtype.Unbounded), + Upper: tools.DefaultTo(upper, MaxDate()), + Valid: true, + }, + } +} + +var _ driver.Valuer = (*DateRange)(nil) + +func (dr DateRange) Value() (driver.Value, error) { + return assembleRange(dr.Range), nil +} + +var _ sql.Scanner = (*DateRange)(nil) + +func (dr *DateRange) Scan(src interface{}) (err error) { + switch src := src.(type) { + case pgtype.Range[Date]: + dr.Range = src + case string: + r, err := destructureToRange[Date](src) + if err != nil { + return err + } + dr.Range = r + case []byte: + r, err := destructureToRange[Date](string(src)) + if err != nil { + return err + } + dr.Range = r + case nil: + dr = nil + default: + return errors.New("该数据类型不支持解析到日期范围。") + } + return +} + +var _ json.Marshaler = (*DateRange)(nil) + +func (dr DateRange) MarshalJSON() ([]byte, error) { + return json.Marshal(assembleRange(dr.Range)) +} + +var _ json.Unmarshaler = (*DateRange)(nil) + +func (dr *DateRange) UnmarshalJSON(data []byte) error { + var str string + if err := json.Unmarshal(data, &str); err != nil { + return errors.New("不能解析指定的日期范围值。") + } + r, err := destructureToRange[Date](str) + if err != nil { + return err + } + dr.Range = r + return nil +} diff --git a/types/datetime.go b/types/datetime.go index 56e4025..a1c36cc 100644 --- a/types/datetime.go +++ b/types/datetime.go @@ -34,6 +34,18 @@ func NewEmptyDateTime() DateTime { } } +func MinDateTime() DateTime { + return DateTime{ + Time: time.Date(1, 1, 1, 0, 0, 0, 0, loc), + } +} + +func MaxDateTime() DateTime { + return DateTime{ + Time: time.Date(9999, 12, 31, 23, 59, 59, 999999, loc), + } +} + func Timestamp() int64 { startline := time.Date(2022, 2, 22, 22, 22, 22, 0, loc).Unix() return Now().Unix() - startline @@ -159,6 +171,15 @@ func (dt DateTime) IsEmpty() bool { return dt.Time.IsZero() } +func (dt *DateTime) Parse(s string) error { + t, err := ParseDateTime(s) + if err != nil { + return err + } + dt.Time = t.Time + return nil +} + func (dt DateTime) ToString() string { return dt.Time.Format("2006-01-02 15:04:05") } diff --git a/types/datetimerange.go b/types/datetimerange.go new file mode 100644 index 0000000..46e9562 --- /dev/null +++ b/types/datetimerange.go @@ -0,0 +1,80 @@ +package types + +import ( + "database/sql" + "database/sql/driver" + "electricity_bill_calc/tools" + "encoding/json" + "errors" + + "github.com/jackc/pgx/v5/pgtype" +) + +type DateTimeRange struct { + pgtype.Range[DateTime] +} + +func NewDateTimeRange(lower *DateTime, upper *DateTime) DateTimeRange { + return DateTimeRange{ + Range: pgtype.Range[DateTime]{ + LowerType: tools.Cond(lower != nil, pgtype.Inclusive, pgtype.Unbounded), + Lower: tools.DefaultTo(lower, MinDateTime()), + UpperType: tools.Cond(upper != nil, pgtype.Inclusive, pgtype.Unbounded), + Upper: tools.DefaultTo(upper, MaxDateTime()), + Valid: true, + }, + } +} + +var _ driver.Value = (*DateTimeRange)(nil) + +func (dr DateTimeRange) Value() (driver.Value, error) { + return assembleRange(dr.Range), nil +} + +var _ sql.Scanner = (*DateTimeRange)(nil) + +func (dr *DateTimeRange) Scan(src interface{}) (err error) { + switch src := src.(type) { + case pgtype.Range[DateTime]: + dr.Range = src + case string: + r, err := destructureToRange[DateTime](src) + if err != nil { + return err + } + dr.Range = r + case []byte: + r, err := destructureToRange[DateTime](string(src)) + if err != nil { + return err + } + dr.Range = r + case nil: + dr = nil + default: + return errors.New("该数据类型不支持解析到日期范围。") + } + return +} + +var _ json.Marshaler = (*DateTimeRange)(nil) + +func (dr DateTimeRange) MarshalJSON() ([]byte, error) { + return json.Marshal(assembleRange(dr.Range)) +} + +var _ json.Unmarshaler = (*DateTimeRange)(nil) + +func (dr *DateTimeRange) UnmarshalJSON(data []byte) error { + var str string + if err := json.Unmarshal(data, &str); err != nil { + return errors.New("不能解析指定的日期范围值。") + } + r, err := destructureToRange[DateTime](str) + if err != nil { + return err + } + dr.Range = r + return nil +} diff --git a/types/range.go b/types/range.go new file mode 100644 index 0000000..178b59f --- /dev/null +++ b/types/range.go @@ -0,0 +1,115 @@ +package types + +import ( + "errors" + "strings" + + "github.com/jackc/pgx/v5/pgtype" +) + +type Parse interface { + Parse(string) error +} + +type ToString interface { + ToString() string +} + +// 将一个字符串拆解解析为一个 Postgresql 范围类型的值。 +func destructureToRange[T any, PT interface { + Parse + *T +}](s string) (pgtype.Range[T], error) { + var r pgtype.Range[T] + r.Valid = false + if len(s) == 0 { + r.LowerType = pgtype.Empty + r.UpperType = pgtype.Empty + return r, nil + } + rangeUnit := strings.Split(s, ",") + if len(rangeUnit) != 2 { + return r, errors.New("无法解析给定的范围值,格式不正确。") + } + if unit, found := strings.CutPrefix(rangeUnit[0], "["); found { + var t PT + if len(unit) > 0 { + r.LowerType = pgtype.Inclusive + err := t.Parse(unit) + if err != nil { + return r, errors.New("无法解析给定的最低范围值。") + } + } else { + r.LowerType = pgtype.Unbounded + } + r.Lower = *t + } + if unit, found := strings.CutPrefix(rangeUnit[0], "("); found { + var t PT + if len(unit) > 0 { + r.LowerType = pgtype.Exclusive + err := t.Parse(unit) + if err != nil { + return r, errors.New("无法解析给定的最低范围值。") + } + } else { + r.LowerType = pgtype.Unbounded + } + r.Lower = *t + } + + if unit, found := strings.CutSuffix(rangeUnit[1], "]"); found { + var t PT + if len(unit) > 0 { + r.UpperType = pgtype.Inclusive + err := t.Parse(unit) + if err != nil { + return r, errors.New("无法解析给定的最高范围值。") + } + } else { + r.UpperType = pgtype.Unbounded + } + r.Upper = *t + } + if unit, found := strings.CutSuffix(rangeUnit[1], ")"); found { + var t PT + if len(unit) > 0 { + r.UpperType = pgtype.Exclusive + err := t.Parse(unit) + if err != nil { + return r, errors.New("无法解析给定的最高范围值。") + } + } else { + r.UpperType = pgtype.Unbounded + } + r.Upper = *t + } + r.Valid = true + return r, nil +} + +// 将一个范围类型的值转换为一个字符串、 +func assembleRange[T ToString](r pgtype.Range[T]) string { + var sb strings.Builder + if r.LowerType == pgtype.Empty || r.UpperType == pgtype.Empty { + return "empty" + } + if r.LowerType == pgtype.Inclusive { + sb.WriteString("[") + } else { + sb.WriteString("(") + } + if r.LowerType != pgtype.Unbounded { + sb.WriteString(r.Lower.ToString()) + } + sb.WriteString(",") + if r.UpperType != pgtype.Unbounded { + sb.WriteString(r.Upper.ToString()) + } + if r.UpperType == pgtype.Inclusive { + sb.WriteString("]") + } else { + sb.WriteString(")") + } + return sb.String() +}