forked from free-lancers/electricity_bill_calc_service
		
	feat(types):增加日期时间的范围区间类型。
This commit is contained in:
		| @@ -153,6 +153,15 @@ func (d Date) IsEmpty() bool { | |||||||
| 	return d.Time.IsZero() | 	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 { | func (d Date) ToString() string { | ||||||
| 	return d.Time.Format("2006-01-02") | 	return d.Time.Format("2006-01-02") | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										80
									
								
								types/daterange.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								types/daterange.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
|  | } | ||||||
| @@ -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 { | func Timestamp() int64 { | ||||||
| 	startline := time.Date(2022, 2, 22, 22, 22, 22, 0, loc).Unix() | 	startline := time.Date(2022, 2, 22, 22, 22, 22, 0, loc).Unix() | ||||||
| 	return Now().Unix() - startline | 	return Now().Unix() - startline | ||||||
| @@ -159,6 +171,15 @@ func (dt DateTime) IsEmpty() bool { | |||||||
| 	return dt.Time.IsZero() | 	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 { | func (dt DateTime) ToString() string { | ||||||
| 	return dt.Time.Format("2006-01-02 15:04:05") | 	return dt.Time.Format("2006-01-02 15:04:05") | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										80
									
								
								types/datetimerange.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								types/datetimerange.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
|  | } | ||||||
							
								
								
									
										115
									
								
								types/range.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								types/range.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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() | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user