github.com/jackc/pgx/v5@v5.5.5/pgtype/time.go (about) 1 package pgtype 2 3 import ( 4 "database/sql/driver" 5 "encoding/binary" 6 "fmt" 7 "strconv" 8 9 "github.com/jackc/pgx/v5/internal/pgio" 10 ) 11 12 type TimeScanner interface { 13 ScanTime(v Time) error 14 } 15 16 type TimeValuer interface { 17 TimeValue() (Time, error) 18 } 19 20 // Time represents the PostgreSQL time type. The PostgreSQL time is a time of day without time zone. 21 // 22 // Time is represented as the number of microseconds since midnight in the same way that PostgreSQL does. Other time 23 // and date types in pgtype can use time.Time as the underlying representation. However, pgtype.Time type cannot due 24 // to needing to handle 24:00:00. time.Time converts that to 00:00:00 on the following day. 25 type Time struct { 26 Microseconds int64 // Number of microseconds since midnight 27 Valid bool 28 } 29 30 func (t *Time) ScanTime(v Time) error { 31 *t = v 32 return nil 33 } 34 35 func (t Time) TimeValue() (Time, error) { 36 return t, nil 37 } 38 39 // Scan implements the database/sql Scanner interface. 40 func (t *Time) Scan(src any) error { 41 if src == nil { 42 *t = Time{} 43 return nil 44 } 45 46 switch src := src.(type) { 47 case string: 48 return scanPlanTextAnyToTimeScanner{}.Scan([]byte(src), t) 49 } 50 51 return fmt.Errorf("cannot scan %T", src) 52 } 53 54 // Value implements the database/sql/driver Valuer interface. 55 func (t Time) Value() (driver.Value, error) { 56 if !t.Valid { 57 return nil, nil 58 } 59 60 buf, err := TimeCodec{}.PlanEncode(nil, 0, TextFormatCode, t).Encode(t, nil) 61 if err != nil { 62 return nil, err 63 } 64 return string(buf), err 65 } 66 67 type TimeCodec struct{} 68 69 func (TimeCodec) FormatSupported(format int16) bool { 70 return format == TextFormatCode || format == BinaryFormatCode 71 } 72 73 func (TimeCodec) PreferredFormat() int16 { 74 return BinaryFormatCode 75 } 76 77 func (TimeCodec) PlanEncode(m *Map, oid uint32, format int16, value any) EncodePlan { 78 if _, ok := value.(TimeValuer); !ok { 79 return nil 80 } 81 82 switch format { 83 case BinaryFormatCode: 84 return encodePlanTimeCodecBinary{} 85 case TextFormatCode: 86 return encodePlanTimeCodecText{} 87 } 88 89 return nil 90 } 91 92 type encodePlanTimeCodecBinary struct{} 93 94 func (encodePlanTimeCodecBinary) Encode(value any, buf []byte) (newBuf []byte, err error) { 95 t, err := value.(TimeValuer).TimeValue() 96 if err != nil { 97 return nil, err 98 } 99 100 if !t.Valid { 101 return nil, nil 102 } 103 104 return pgio.AppendInt64(buf, t.Microseconds), nil 105 } 106 107 type encodePlanTimeCodecText struct{} 108 109 func (encodePlanTimeCodecText) Encode(value any, buf []byte) (newBuf []byte, err error) { 110 t, err := value.(TimeValuer).TimeValue() 111 if err != nil { 112 return nil, err 113 } 114 115 if !t.Valid { 116 return nil, nil 117 } 118 119 usec := t.Microseconds 120 hours := usec / microsecondsPerHour 121 usec -= hours * microsecondsPerHour 122 minutes := usec / microsecondsPerMinute 123 usec -= minutes * microsecondsPerMinute 124 seconds := usec / microsecondsPerSecond 125 usec -= seconds * microsecondsPerSecond 126 127 s := fmt.Sprintf("%02d:%02d:%02d.%06d", hours, minutes, seconds, usec) 128 129 return append(buf, s...), nil 130 } 131 132 func (TimeCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan { 133 134 switch format { 135 case BinaryFormatCode: 136 switch target.(type) { 137 case TimeScanner: 138 return scanPlanBinaryTimeToTimeScanner{} 139 } 140 case TextFormatCode: 141 switch target.(type) { 142 case TimeScanner: 143 return scanPlanTextAnyToTimeScanner{} 144 } 145 } 146 147 return nil 148 } 149 150 type scanPlanBinaryTimeToTimeScanner struct{} 151 152 func (scanPlanBinaryTimeToTimeScanner) Scan(src []byte, dst any) error { 153 scanner := (dst).(TimeScanner) 154 155 if src == nil { 156 return scanner.ScanTime(Time{}) 157 } 158 159 if len(src) != 8 { 160 return fmt.Errorf("invalid length for time: %v", len(src)) 161 } 162 163 usec := int64(binary.BigEndian.Uint64(src)) 164 165 return scanner.ScanTime(Time{Microseconds: usec, Valid: true}) 166 } 167 168 type scanPlanTextAnyToTimeScanner struct{} 169 170 func (scanPlanTextAnyToTimeScanner) Scan(src []byte, dst any) error { 171 scanner := (dst).(TimeScanner) 172 173 if src == nil { 174 return scanner.ScanTime(Time{}) 175 } 176 177 s := string(src) 178 179 if len(s) < 8 { 180 return fmt.Errorf("cannot decode %v into Time", s) 181 } 182 183 hours, err := strconv.ParseInt(s[0:2], 10, 64) 184 if err != nil { 185 return fmt.Errorf("cannot decode %v into Time", s) 186 } 187 usec := hours * microsecondsPerHour 188 189 minutes, err := strconv.ParseInt(s[3:5], 10, 64) 190 if err != nil { 191 return fmt.Errorf("cannot decode %v into Time", s) 192 } 193 usec += minutes * microsecondsPerMinute 194 195 seconds, err := strconv.ParseInt(s[6:8], 10, 64) 196 if err != nil { 197 return fmt.Errorf("cannot decode %v into Time", s) 198 } 199 usec += seconds * microsecondsPerSecond 200 201 if len(s) > 9 { 202 fraction := s[9:] 203 n, err := strconv.ParseInt(fraction, 10, 64) 204 if err != nil { 205 return fmt.Errorf("cannot decode %v into Time", s) 206 } 207 208 for i := len(fraction); i < 6; i++ { 209 n *= 10 210 } 211 212 usec += n 213 } 214 215 return scanner.ScanTime(Time{Microseconds: usec, Valid: true}) 216 } 217 218 func (c TimeCodec) DecodeDatabaseSQLValue(m *Map, oid uint32, format int16, src []byte) (driver.Value, error) { 219 return codecDecodeToTextFormat(c, m, oid, format, src) 220 } 221 222 func (c TimeCodec) DecodeValue(m *Map, oid uint32, format int16, src []byte) (any, error) { 223 if src == nil { 224 return nil, nil 225 } 226 227 var t Time 228 err := codecScan(c, m, oid, format, src, &t) 229 if err != nil { 230 return nil, err 231 } 232 return t, nil 233 }