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  }