github.com/matrixorigin/matrixone@v0.7.0/pkg/sql/plan/function/builtin/binary/date_format.go (about) 1 // Copyright 2021 - 2022 Matrix Origin 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package binary 16 17 import ( 18 "bytes" 19 "context" 20 "fmt" 21 "math" 22 "strconv" 23 24 "github.com/matrixorigin/matrixone/pkg/common/moerr" 25 "github.com/matrixorigin/matrixone/pkg/container/nulls" 26 "github.com/matrixorigin/matrixone/pkg/container/types" 27 "github.com/matrixorigin/matrixone/pkg/container/vector" 28 "github.com/matrixorigin/matrixone/pkg/vm/process" 29 ) 30 31 // function overview: 32 // date_format() function used to formated the date value according to the format string. If either argument is NULL, the function returns NULL. 33 // Input parameter type: date type: datatime, format type: string(constant) 34 // return type: string 35 // reference linking:https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_date-format 36 37 var ( 38 // WeekdayNames lists names of weekdays, which are used in builtin function `date_format`. 39 WeekdayNames = []string{ 40 "Monday", 41 "Tuesday", 42 "Wednesday", 43 "Thursday", 44 "Friday", 45 "Saturday", 46 "Sunday", 47 } 48 49 // MonthNames lists names of months, which are used in builtin function `date_format`. 50 MonthNames = []string{ 51 "January", 52 "February", 53 "March", 54 "April", 55 "May", 56 "June", 57 "July", 58 "August", 59 "September", 60 "October", 61 "November", 62 "December", 63 } 64 65 // AbbrevWeekdayName lists Abbreviation of week names, which are used int builtin function 'date_format' 66 AbbrevWeekdayName = []string{ 67 "Sun", 68 "Mon", 69 "Tue", 70 "Wed", 71 "Thu", 72 "Fri", 73 "Sat", 74 } 75 ) 76 77 // DateFromat: Formats the date value according to the format string. If either argument is NULL, the function returns NULL. 78 func DateFormat(vectors []*vector.Vector, proc *process.Process) (*vector.Vector, error) { 79 dateVector := vectors[0] 80 formatVector := vectors[1] 81 82 resultType := types.T_varchar.ToType() 83 if !formatVector.IsScalar() { 84 return nil, moerr.NewInvalidArg(proc.Ctx, "date format format", "not constant") 85 } 86 87 if dateVector.IsScalarNull() || formatVector.IsScalarNull() { 88 return proc.AllocScalarNullVector(resultType), nil 89 } 90 91 // get the format string. 92 formatMask := string(formatVector.GetString(0)) 93 94 if dateVector.IsScalar() { 95 // XXX Null handling maybe broken. 96 datetimes := dateVector.Col.([]types.Datetime) 97 resCol, err := CalcDateFromat(proc.Ctx, datetimes, formatMask, dateVector.Nsp) 98 if err != nil { 99 return nil, err 100 } 101 return vector.NewConstString(resultType, 1, resCol[0], proc.Mp()), nil 102 } else { 103 datetimes := dateVector.Col.([]types.Datetime) 104 resCol, err := CalcDateFromat(proc.Ctx, datetimes, formatMask, dateVector.Nsp) 105 if err != nil { 106 return nil, err 107 } 108 resultVector := vector.New(resultType) 109 resultVector.Nsp = dateVector.Nsp 110 vector.AppendString(resultVector, resCol, proc.Mp()) 111 return resultVector, nil 112 } 113 } 114 115 // CalcDateFromat: DateFromat is used to formating the datetime values according to the format string. 116 func CalcDateFromat(ctx context.Context, datetimes []types.Datetime, format string, ns *nulls.Nulls) ([]string, error) { 117 res := make([]string, len(datetimes)) 118 for idx, datetime := range datetimes { 119 if nulls.Contains(ns, uint64(idx)) { 120 continue 121 } 122 formatStr, err := datetimeFormat(ctx, datetime, format) 123 if err != nil { 124 return nil, err 125 } 126 res[idx] = formatStr 127 } 128 return res, nil 129 } 130 131 // datetimeFormat: format the datetime value according to the format string. 132 func datetimeFormat(ctx context.Context, datetime types.Datetime, format string) (string, error) { 133 var buf bytes.Buffer 134 inPatternMatch := false 135 for _, b := range format { 136 if inPatternMatch { 137 if err := makeDateFormat(ctx, datetime, b, &buf); err != nil { 138 return "", err 139 } 140 inPatternMatch = false 141 continue 142 } 143 144 // It's not in pattern match now. 145 if b == '%' { 146 inPatternMatch = true 147 } else { 148 buf.WriteRune(b) 149 } 150 } 151 return buf.String(), nil 152 } 153 154 // makeDateFormat: Get the format string corresponding to the date according to a single format character 155 func makeDateFormat(ctx context.Context, t types.Datetime, b rune, buf *bytes.Buffer) error { 156 switch b { 157 case 'b': 158 m := t.Month() 159 if m == 0 || m > 12 { 160 return moerr.NewInvalidInput(ctx, "invalud date format for month '%d'", m) 161 } 162 buf.WriteString(MonthNames[m-1][:3]) 163 case 'M': 164 m := t.Month() 165 if m == 0 || m > 12 { 166 return moerr.NewInvalidInput(ctx, "invalud date format for month '%d'", m) 167 } 168 buf.WriteString(MonthNames[m-1]) 169 case 'm': 170 buf.WriteString(FormatIntByWidth(int(t.Month()), 2)) 171 case 'c': 172 buf.WriteString(strconv.FormatInt(int64(t.Month()), 10)) 173 case 'D': 174 buf.WriteString(strconv.FormatInt(int64(t.Day()), 10)) 175 buf.WriteString(AbbrDayOfMonth(int(t.Day()))) 176 case 'd': 177 buf.WriteString(FormatIntByWidth(int(t.Day()), 2)) 178 case 'e': 179 buf.WriteString(strconv.FormatInt(int64(t.Day()), 10)) 180 case 'f': 181 fmt.Fprintf(buf, "%06d", t.MicroSec()) 182 case 'j': 183 fmt.Fprintf(buf, "%03d", t.DayOfYear()) 184 case 'H': 185 buf.WriteString(FormatIntByWidth(int(t.Hour()), 2)) 186 case 'k': 187 buf.WriteString(strconv.FormatInt(int64(t.Hour()), 10)) 188 case 'h', 'I': 189 tt := t.Hour() 190 if tt%12 == 0 { 191 buf.WriteString("12") 192 } else { 193 buf.WriteString(FormatIntByWidth(int(tt%12), 2)) 194 } 195 case 'i': 196 buf.WriteString(FormatIntByWidth(int(t.Minute()), 2)) 197 case 'l': 198 tt := t.Hour() 199 if tt%12 == 0 { 200 buf.WriteString("12") 201 } else { 202 buf.WriteString(strconv.FormatInt(int64(tt%12), 10)) 203 } 204 case 'p': 205 hour := t.Hour() 206 if hour/12%2 == 0 { 207 buf.WriteString("AM") 208 } else { 209 buf.WriteString("PM") 210 } 211 case 'r': 212 h := t.Hour() 213 h %= 24 214 switch { 215 case h == 0: 216 fmt.Fprintf(buf, "%02d:%02d:%02d AM", 12, t.Minute(), t.Sec()) 217 case h == 12: 218 fmt.Fprintf(buf, "%02d:%02d:%02d PM", 12, t.Minute(), t.Sec()) 219 case h < 12: 220 fmt.Fprintf(buf, "%02d:%02d:%02d AM", h, t.Minute(), t.Sec()) 221 default: 222 fmt.Fprintf(buf, "%02d:%02d:%02d PM", h-12, t.Minute(), t.Sec()) 223 } 224 case 'S', 's': 225 buf.WriteString(FormatIntByWidth(int(t.Sec()), 2)) 226 case 'T': 227 fmt.Fprintf(buf, "%02d:%02d:%02d", t.Hour(), t.Minute(), t.Sec()) 228 case 'U': 229 w := t.Week(0) 230 buf.WriteString(FormatIntByWidth(w, 2)) 231 case 'u': 232 w := t.Week(1) 233 buf.WriteString(FormatIntByWidth(w, 2)) 234 case 'V': 235 w := t.Week(2) 236 buf.WriteString(FormatIntByWidth(w, 2)) 237 case 'v': 238 _, w := t.YearWeek(3) 239 buf.WriteString(FormatIntByWidth(w, 2)) 240 case 'a': 241 weekday := t.DayOfWeek() 242 buf.WriteString(AbbrevWeekdayName[weekday]) 243 case 'W': 244 buf.WriteString(t.DayOfWeek().String()) 245 case 'w': 246 buf.WriteString(strconv.FormatInt(int64(t.DayOfWeek()), 10)) 247 case 'X': 248 year, _ := t.YearWeek(2) 249 if year < 0 { 250 buf.WriteString(strconv.FormatUint(uint64(math.MaxUint32), 10)) 251 } else { 252 buf.WriteString(FormatIntByWidth(year, 4)) 253 } 254 case 'x': 255 year, _ := t.YearWeek(3) 256 if year < 0 { 257 buf.WriteString(strconv.FormatUint(uint64(math.MaxUint32), 10)) 258 } else { 259 buf.WriteString(FormatIntByWidth(year, 4)) 260 } 261 case 'Y': 262 buf.WriteString(FormatIntByWidth(int(t.Year()), 4)) 263 case 'y': 264 str := FormatIntByWidth(int(t.Year()), 4) 265 buf.WriteString(str[2:]) 266 default: 267 buf.WriteRune(b) 268 } 269 return nil 270 } 271 272 // FormatIntByWidth: Formatintwidthn is used to format ints with width parameter n. Insufficient numbers are filled with 0. 273 func FormatIntByWidth(num, n int) string { 274 numStr := strconv.FormatInt(int64(num), 10) 275 if len(numStr) >= n { 276 return numStr 277 } 278 padBytes := make([]byte, n-len(numStr)) 279 for i := range padBytes { 280 padBytes[i] = '0' 281 } 282 return string(padBytes) + numStr 283 } 284 285 // AbbrDayOfMonth: Get the abbreviation of month of day 286 func AbbrDayOfMonth(day int) string { 287 var str string 288 switch day { 289 case 1, 21, 31: 290 str = "st" 291 case 2, 22: 292 str = "nd" 293 case 3, 23: 294 str = "rd" 295 default: 296 str = "th" 297 } 298 return str 299 }