github.com/matrixorigin/matrixone@v1.2.0/pkg/vectorize/format/format.go (about)

     1  // Copyright 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 format
    16  
    17  import (
    18  	"bytes"
    19  	"strconv"
    20  	"strings"
    21  	"unicode"
    22  
    23  	"github.com/matrixorigin/matrixone/pkg/common/moerr"
    24  )
    25  
    26  // FormatFunc is the locale format function signature.
    27  type FormatFunc func(string, string) (string, error)
    28  
    29  func GetNumberFormat(number, scale, locale string) (string, error) {
    30  	return getFormatFunctionWithLocale(locale)(number, scale)
    31  }
    32  
    33  // GetFormatFunctionWithLocate get the format function for sepcific locale.
    34  func getFormatFunctionWithLocale(locale string) FormatFunc {
    35  	formatFunc, exist := localeToFormatFunction[locale]
    36  	if !exist {
    37  		return formatENUS
    38  	}
    39  	return formatFunc
    40  }
    41  
    42  // localeToFormatFunction is the string represent of locale format function.
    43  var localeToFormatFunction = map[string]FormatFunc{
    44  	//formatENUS
    45  	"ar_AE": formatENUS,
    46  	"ar_BH": formatENUS,
    47  	"ar_DZ": formatENUS,
    48  	"ar_EG": formatENUS,
    49  	"ar_IN": formatENUS,
    50  	"ar_IQ": formatENUS,
    51  	"ar_JO": formatENUS,
    52  	"ar_KW": formatENUS,
    53  	"ar_LB": formatENUS,
    54  	"ar_LY": formatENUS,
    55  	"ar_MA": formatENUS,
    56  	"ar_OM": formatENUS,
    57  	"ar_QA": formatENUS,
    58  	"ar_SD": formatENUS,
    59  	"ar_SY": formatENUS,
    60  	"ar_TN": formatENUS,
    61  	"ar_YE": formatENUS,
    62  	"en_AU": formatENUS,
    63  	"en_CA": formatENUS,
    64  	"en_GB": formatENUS,
    65  	"en_IN": formatENUS,
    66  	"en_NZ": formatENUS,
    67  	"en_PH": formatENUS,
    68  	"en_US": formatENUS,
    69  	"en_ZA": formatENUS,
    70  	"en_ZW": formatENUS,
    71  	"es_DO": formatENUS,
    72  	"es_GT": formatENUS,
    73  	"es_HN": formatENUS,
    74  	"en_MX": formatENUS,
    75  	"es_NI": formatENUS,
    76  	"es_PA": formatENUS,
    77  	"es_PR": formatENUS,
    78  	"es_SV": formatENUS,
    79  	"es_US": formatENUS,
    80  	"gu_IN": formatENUS,
    81  	"he_IL": formatENUS,
    82  	"hi_IN": formatENUS,
    83  	"ja_JP": formatENUS,
    84  	"ko_KR": formatENUS,
    85  	"ms_MY": formatENUS,
    86  	"ta_IN": formatENUS,
    87  	"te_IN": formatENUS,
    88  	"th_TH": formatENUS,
    89  	"ur_PK": formatENUS,
    90  	"zh_CN": formatENUS,
    91  	"zh_HK": formatENUS,
    92  	"zh_TW": formatENUS,
    93  
    94  	//formatARSA
    95  	"ar_SA": formatARSA,
    96  	"sr_RS": formatARSA,
    97  
    98  	//formatBEBY
    99  	"be_BY": formatBEBY,
   100  	"da_DK": formatBEBY,
   101  	"de_BE": formatBEBY,
   102  	"de_DE": formatBEBY,
   103  	"de_LU": formatBEBY,
   104  	"es_AR": formatBEBY,
   105  	"es_BO": formatBEBY,
   106  	"es_CL": formatBEBY,
   107  	"es_CO": formatBEBY,
   108  	"es_EC": formatBEBY,
   109  	"es_ES": formatBEBY,
   110  	"es_PY": formatBEBY,
   111  	"es_UY": formatBEBY,
   112  	"es_VE": formatBEBY,
   113  	"fo_FO": formatBEBY,
   114  	"hu_HU": formatBEBY,
   115  	"id_ID": formatBEBY,
   116  	"is_IS": formatBEBY,
   117  	"lt_LT": formatBEBY,
   118  	"mn_MN": formatBEBY,
   119  	"nb_NO": formatBEBY,
   120  	"no_NO": formatBEBY,
   121  	"ru_UA": formatBEBY,
   122  	"sq_AL": formatBEBY,
   123  	"tr_TR": formatBEBY,
   124  	"uk_UA": formatBEBY,
   125  	"vi_VN": formatBEBY,
   126  	"ro_RO": formatBEBY,
   127  
   128  	//formatBGBG
   129  	"bg_BG": formatBGBG,
   130  	"cs_CZ": formatBGBG,
   131  	"es_CR": formatBGBG,
   132  	"et_EE": formatBGBG,
   133  	"fi_FI": formatBGBG,
   134  	"lv_LV": formatBGBG,
   135  	"mk_MK": formatBGBG,
   136  	"ru_RU": formatBGBG,
   137  	"sk_SK": formatBGBG,
   138  	"sv_FI": formatBGBG,
   139  	"sv_SE": formatBGBG,
   140  
   141  	//formatDECH
   142  	"de_CH": formatDECH,
   143  
   144  	//formatCAES
   145  	"ca_ES": formatCAES,
   146  	"de_AT": formatCAES,
   147  	"el_GR": formatCAES,
   148  	"eu_ES": formatCAES,
   149  	"fr_BE": formatCAES,
   150  	"fr_CA": formatCAES,
   151  	"fr_CH": formatCAES,
   152  	"fr_FR": formatCAES,
   153  	"fr_LU": formatCAES,
   154  	"gl_ES": formatCAES,
   155  	"hr_HR": formatCAES,
   156  	"it_IT": formatCAES,
   157  	"nl_BE": formatCAES,
   158  	"nl_NL": formatCAES,
   159  	"pl_PL": formatCAES,
   160  	"pt_BR": formatCAES,
   161  	"pt_PT": formatCAES,
   162  	"sl_SI": formatCAES,
   163  
   164  	//formatITCH
   165  	"it_CH": formatITCH,
   166  	"rm_CH": formatITCH,
   167  }
   168  
   169  // format number like 20,000,000.0000
   170  func formatENUS(number string, scale string) (string, error) {
   171  	return format(number, scale, []byte{','}, []byte{'.'})
   172  }
   173  
   174  // format number like 20000000.0000
   175  func formatARSA(number string, scale string) (string, error) {
   176  	return format(number, scale, []byte{}, []byte{'.'})
   177  }
   178  
   179  // format number like 20.000.000,0000
   180  func formatBEBY(number string, scale string) (string, error) {
   181  	return format(number, scale, []byte{'.'}, []byte{','})
   182  }
   183  
   184  // format number like 20 000 000,0000
   185  func formatBGBG(number string, scale string) (string, error) {
   186  	return format(number, scale, []byte{' '}, []byte{','})
   187  }
   188  
   189  // format number like 20'000'000.0000
   190  func formatDECH(number string, scale string) (string, error) {
   191  	return format(number, scale, []byte{'\''}, []byte{'.'})
   192  }
   193  
   194  // format number like 20000000,0000
   195  func formatCAES(number string, scale string) (string, error) {
   196  	return format(number, scale, []byte{}, []byte{','})
   197  }
   198  
   199  // format number like 20'000'000.0000
   200  func formatITCH(number string, scale string) (string, error) {
   201  	return format(number, scale, []byte{'\''}, []byte{','})
   202  }
   203  
   204  func format(number string, scale string, comma, decimalPoint []byte) (string, error) {
   205  	var buffer bytes.Buffer
   206  
   207  	if len(number) == 0 {
   208  		return "", nil
   209  	}
   210  	//handle scale
   211  	if unicode.IsDigit(rune(scale[0])) {
   212  		for i, v := range scale {
   213  			if unicode.IsDigit(v) {
   214  				continue
   215  			}
   216  			scale = scale[:i]
   217  			break
   218  		}
   219  	} else {
   220  		scale = "0"
   221  	}
   222  
   223  	//handle number
   224  	if number[0] == '-' && number[1] == '.' {
   225  		number = strings.Replace(number, "-", "-0", 1)
   226  	} else if number[0] == '.' {
   227  		number = strings.Replace(number, ".", "0.", 1)
   228  	}
   229  
   230  	if (number[:1] == "-" && !unicode.IsDigit(rune(number[1]))) ||
   231  		(!unicode.IsDigit(rune(number[0])) && number[:1] != "-") {
   232  		buffer.Write([]byte{'0'})
   233  		position, err := strconv.ParseUint(scale, 10, 64)
   234  		if err == nil && position > 0 {
   235  			buffer.Write([]byte{'.'})
   236  			buffer.WriteString(strings.Repeat("0", int(position)))
   237  		}
   238  		return buffer.String(), nil
   239  	} else if number[:1] == "-" {
   240  		buffer.Write([]byte{'-'})
   241  		number = number[1:]
   242  	}
   243  
   244  	// Check for scientific notition.
   245  	for _, v := range number {
   246  		if v == 'E' || v == 'e' {
   247  			num, err := strconv.ParseFloat(number, 64)
   248  			if err != nil {
   249  				return "", err
   250  			}
   251  			// Convert to non-scientific notition.
   252  			number = strconv.FormatFloat(num, 'f', -1, 64)
   253  			break
   254  		}
   255  	}
   256  
   257  	for i, v := range number {
   258  		if unicode.IsDigit(v) {
   259  			continue
   260  		} else if i == 1 && number[1] == '.' {
   261  			continue
   262  		} else if v == '.' && number[1] != '.' {
   263  			continue
   264  		} else {
   265  			number = number[:i]
   266  			break
   267  		}
   268  	}
   269  
   270  	parts := strings.Split(number, ".")
   271  
   272  	if len(comma) != 0 {
   273  		addComma(comma, &buffer, parts[0])
   274  	} else {
   275  		buffer.WriteString(parts[0])
   276  	}
   277  
   278  	//According to the scale to process the decimal parts
   279  	position, err := strconv.ParseUint(scale, 10, 64)
   280  	if err != nil {
   281  		return "", err
   282  	}
   283  
   284  	if position > 0 {
   285  		buffer.Write(decimalPoint)
   286  		if len(parts) == 2 {
   287  			if uint64(len(parts[1])) == position {
   288  				buffer.WriteString(parts[1][:position])
   289  			} else if uint64(len(parts[1])) > position {
   290  				//need to cast decimal's length
   291  				if parts[1][position:position+1] >= "5" {
   292  					buffer.Reset()
   293  					floatVar, err := strconv.ParseFloat(parts[0]+"."+parts[1], 64)
   294  					if err != nil {
   295  						return "", moerr.NewInvalidArgNoCtx("format parser error", parts[0]+"."+parts[1])
   296  					}
   297  					newNumber := strconv.FormatFloat(floatVar, 'f', int(position), 64)
   298  					newParts := strings.Split(newNumber, ".")
   299  
   300  					if len(comma) != 0 {
   301  						addComma(comma, &buffer, newParts[0])
   302  					} else {
   303  						buffer.WriteString(newParts[0])
   304  					}
   305  
   306  					buffer.Write(decimalPoint)
   307  					buffer.WriteString(newParts[1])
   308  				} else {
   309  					buffer.WriteString(parts[1][:position])
   310  				}
   311  			} else {
   312  				buffer.WriteString(parts[1])
   313  				buffer.WriteString(strings.Repeat("0", int(position)-len(parts[1])))
   314  			}
   315  		} else {
   316  			buffer.WriteString(strings.Repeat("0", int(position)))
   317  		}
   318  	}
   319  
   320  	return buffer.String(), nil
   321  }
   322  
   323  func addComma(comma []byte, buffer *bytes.Buffer, formatString string) {
   324  	pos := 0
   325  
   326  	//Add foramt comma for Integr parts
   327  	//If the integer part's length larger than 3
   328  	if len(formatString)%3 != 0 {
   329  		pos += len(formatString) % 3
   330  		buffer.WriteString(formatString[:pos])
   331  		buffer.Write(comma)
   332  	}
   333  	//Add a format comma every three digits
   334  	for ; pos < len(formatString); pos += 3 {
   335  		buffer.WriteString(formatString[pos : pos+3])
   336  		buffer.Write(comma)
   337  	}
   338  
   339  	buffer.Truncate(buffer.Len() - 1)
   340  
   341  }