...

Source file src/cloud.google.com/go/bigquery/intervalvalue.go

Documentation: cloud.google.com/go/bigquery

     1  // Copyright 2022 Google LLC
     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 bigquery
    16  
    17  import (
    18  	"bytes"
    19  	"fmt"
    20  	"strconv"
    21  	"time"
    22  )
    23  
    24  // IntervalValue is a go type for representing BigQuery INTERVAL values.
    25  // Intervals are represented using three distinct parts:
    26  // * Years and Months
    27  // * Days
    28  // * Time (Hours/Mins/Seconds/Fractional Seconds).
    29  //
    30  // More information about BigQuery INTERVAL types can be found at:
    31  // https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#interval_type
    32  //
    33  // IntervalValue is EXPERIMENTAL and subject to change or removal without notice.
    34  type IntervalValue struct {
    35  	// In canonical form, Years and Months share a consistent sign and reduced
    36  	// to avoid large month values.
    37  	Years  int32
    38  	Months int32
    39  
    40  	// In canonical form, Days are independent of the other parts and can have it's
    41  	// own sign.  There is no attempt to reduce larger Day values into the Y-M part.
    42  	Days int32
    43  
    44  	// In canonical form, the time parts all share a consistent sign and are reduced.
    45  	Hours   int32
    46  	Minutes int32
    47  	Seconds int32
    48  	// This represents the fractional seconds as nanoseconds.
    49  	SubSecondNanos int32
    50  }
    51  
    52  // String returns string representation of the interval value using the canonical format.
    53  // The canonical format is as follows:
    54  //
    55  // [sign]Y-M [sign]D [sign]H:M:S[.F]
    56  func (iv *IntervalValue) String() string {
    57  	// Don't canonicalize the current value.  Instead, if it's not canonical,
    58  	// compute the canonical form and use that.
    59  	src := iv
    60  	if !iv.IsCanonical() {
    61  		src = iv.Canonicalize()
    62  	}
    63  	out := fmt.Sprintf("%d-%d %d %d:%d:%d", src.Years, int32abs(src.Months), src.Days, src.Hours, int32abs(src.Minutes), int32abs(src.Seconds))
    64  	if src.SubSecondNanos != 0 {
    65  		mantStr := fmt.Sprintf("%09d", src.SubSecondNanos)
    66  		for len(mantStr) > 0 && mantStr[len(mantStr)-1:] == "0" {
    67  			mantStr = mantStr[0 : len(mantStr)-1]
    68  		}
    69  		out = fmt.Sprintf("%s.%s", out, mantStr)
    70  	}
    71  	return out
    72  }
    73  
    74  // intervalPart is used for parsing string representations.
    75  type intervalPart int
    76  
    77  const (
    78  	yearsPart = iota
    79  	monthsPart
    80  	daysPart
    81  	hoursPart
    82  	minutesPart
    83  	secondsPart
    84  	subsecsPart
    85  )
    86  
    87  func (i intervalPart) String() string {
    88  	knownParts := []string{"YEARS", "MONTHS", "DAYS", "HOURS", "MINUTES", "SECONDS", "SUBSECONDS"}
    89  	if i < 0 || int(i) > len(knownParts) {
    90  		return fmt.Sprintf("UNKNOWN(%d)", i)
    91  	}
    92  	return knownParts[i]
    93  }
    94  
    95  // canonicalParts indicates the parse order for canonical format.
    96  var canonicalParts = []intervalPart{yearsPart, monthsPart, daysPart, hoursPart, minutesPart, secondsPart, subsecsPart}
    97  
    98  // ParseInterval parses an interval in canonical string format and returns the IntervalValue it represents.
    99  func ParseInterval(value string) (*IntervalValue, error) {
   100  	iVal := &IntervalValue{}
   101  	for _, part := range canonicalParts {
   102  		remaining, v, err := getPartValue(part, value)
   103  		if err != nil {
   104  			return nil, err
   105  		}
   106  		switch part {
   107  		case yearsPart:
   108  			iVal.Years = v
   109  		case monthsPart:
   110  			iVal.Months = v
   111  			if iVal.Years < 0 {
   112  				iVal.Months = -v
   113  			}
   114  		case daysPart:
   115  			iVal.Days = v
   116  		case hoursPart:
   117  			iVal.Hours = v
   118  		case minutesPart:
   119  			iVal.Minutes = v
   120  			if iVal.Hours < 0 {
   121  				iVal.Minutes = -v
   122  			}
   123  		case secondsPart:
   124  			iVal.Seconds = v
   125  			if iVal.Hours < 0 {
   126  				iVal.Seconds = -v
   127  			}
   128  		case subsecsPart:
   129  			iVal.SubSecondNanos = v
   130  			if iVal.Hours < 0 {
   131  				iVal.SubSecondNanos = -v
   132  			}
   133  		default:
   134  			return nil, fmt.Errorf("encountered invalid part %s during parse", part)
   135  		}
   136  		value = remaining
   137  	}
   138  	return iVal, nil
   139  }
   140  
   141  func getPartValue(part intervalPart, s string) (string, int32, error) {
   142  	s = trimPrefix(part, s)
   143  	return getNumVal(part, s)
   144  }
   145  
   146  // trimPrefix removes formatting prefix relevant to the given type.
   147  func trimPrefix(part intervalPart, s string) string {
   148  	var trimByte byte
   149  	switch part {
   150  	case yearsPart, daysPart, hoursPart:
   151  		trimByte = byte(' ')
   152  	case monthsPart:
   153  		trimByte = byte('-')
   154  	case minutesPart, secondsPart:
   155  		trimByte = byte(':')
   156  	case subsecsPart:
   157  		trimByte = byte('.')
   158  	}
   159  	for len(s) > 0 && s[0] == trimByte {
   160  		s = s[1:]
   161  	}
   162  	return s
   163  }
   164  
   165  func getNumVal(part intervalPart, s string) (string, int32, error) {
   166  
   167  	allowedVals := []byte("0123456789")
   168  	var allowedSign bool
   169  	captured := ""
   170  	switch part {
   171  	case yearsPart, daysPart, hoursPart:
   172  		allowedSign = true
   173  	}
   174  	// capture sign prefix +/-
   175  	if len(s) > 0 && allowedSign {
   176  		switch s[0] {
   177  		case '-':
   178  			captured = "-"
   179  			s = s[1:]
   180  		case '+':
   181  			s = s[1:]
   182  		}
   183  	}
   184  	for len(s) > 0 && bytes.IndexByte(allowedVals, s[0]) >= 0 {
   185  		captured = captured + string(s[0])
   186  		s = s[1:]
   187  	}
   188  
   189  	if len(captured) == 0 {
   190  		if part == subsecsPart {
   191  			return s, 0, nil
   192  		}
   193  		return "", 0, fmt.Errorf("no value parsed for part %s", part.String())
   194  	}
   195  	// special case: subsecs is a mantissa, convert it to nanos
   196  	if part == subsecsPart {
   197  		parsed, err := strconv.ParseFloat(fmt.Sprintf("0.%s", captured), 64)
   198  		if err != nil {
   199  			return "", 0, fmt.Errorf("couldn't parse %s as %s", captured, part.String())
   200  		}
   201  		return s, int32(parsed * 1e9), nil
   202  	}
   203  	parsed, err := strconv.ParseInt(captured, 10, 32)
   204  	if err != nil {
   205  		return "", 0, fmt.Errorf("error parsing value %s for %s: %w", captured, part.String(), err)
   206  	}
   207  	return s, int32(parsed), nil
   208  }
   209  
   210  // IntervalValueFromDuration converts a time.Duration to an IntervalType representation.
   211  //
   212  // The converted duration only leverages the hours/minutes/seconds part of the interval,
   213  // the other parts representing days, months, and years are not used.
   214  func IntervalValueFromDuration(in time.Duration) *IntervalValue {
   215  	nanos := in.Nanoseconds()
   216  	out := &IntervalValue{}
   217  	out.Hours = int32(nanos / 3600 / 1e9)
   218  	nanos = nanos - (int64(out.Hours) * 3600 * 1e9)
   219  	out.Minutes = int32(nanos / 60 / 1e9)
   220  	nanos = nanos - (int64(out.Minutes) * 60 * 1e9)
   221  	out.Seconds = int32(nanos / 1e9)
   222  	nanos = nanos - (int64(out.Seconds) * 1e9)
   223  	out.SubSecondNanos = int32(nanos)
   224  	return out
   225  }
   226  
   227  // ToDuration converts an interval to a time.Duration value.
   228  //
   229  // For the purposes of conversion:
   230  // Years are normalized to 12 months.
   231  // Months are normalized to 30 days.
   232  // Days are normalized to 24 hours.
   233  func (iv *IntervalValue) ToDuration() time.Duration {
   234  	var accum int64
   235  	accum = 12*int64(iv.Years) + int64(iv.Months)
   236  	// widen to days
   237  	accum = accum*30 + int64(iv.Days)
   238  	// hours
   239  	accum = accum*24 + int64(iv.Hours)
   240  	// minutes
   241  	accum = accum*60 + int64(iv.Minutes)
   242  	// seconds
   243  	accum = accum*60 + int64(iv.Seconds)
   244  	// subsecs
   245  	accum = accum*1e9 + int64(iv.SubSecondNanos*1e9)
   246  	return time.Duration(accum)
   247  }
   248  
   249  // Canonicalize returns an IntervalValue where signs for elements in the
   250  // Y-M and H:M:S.F are consistent and values are normalized/reduced.
   251  //
   252  // Canonical form enables more consistent comparison of the encoded
   253  // interval.  For example, encoding an interval with 12 months is equivalent
   254  // to an interval of 1 year.
   255  func (iv *IntervalValue) Canonicalize() *IntervalValue {
   256  	newIV := &IntervalValue{iv.Years, iv.Months, iv.Days, iv.Hours, iv.Minutes, iv.Seconds, iv.SubSecondNanos}
   257  	// canonicalize Y-M part
   258  	totalMonths := iv.Years*12 + iv.Months
   259  	newIV.Years = totalMonths / 12
   260  	totalMonths = totalMonths - (newIV.Years * 12)
   261  	newIV.Months = totalMonths % 12
   262  
   263  	// No canonicalization for the Days part.
   264  
   265  	// canonicalize time part by switching to Nanos.
   266  	totalNanos := int64(iv.Hours)*3600*1e9 +
   267  		int64(iv.Minutes)*60*1e9 +
   268  		int64(iv.Seconds)*1e9 +
   269  		int64(iv.SubSecondNanos)
   270  
   271  	// Reduce to parts.
   272  	newIV.Hours = int32(totalNanos / 60 / 60 / 1e9)
   273  	totalNanos = totalNanos - (int64(newIV.Hours) * 3600 * 1e9)
   274  	newIV.Minutes = int32(totalNanos / 60 / 1e9)
   275  	totalNanos = totalNanos - (int64(newIV.Minutes) * 60 * 1e9)
   276  	newIV.Seconds = int32(totalNanos / 1e9)
   277  	totalNanos = totalNanos - (int64(newIV.Seconds) * 1e9)
   278  	newIV.SubSecondNanos = int32(totalNanos)
   279  	return newIV
   280  }
   281  
   282  // IsCanonical evaluates whether the current representation is in canonical
   283  // form.
   284  func (iv *IntervalValue) IsCanonical() bool {
   285  	if !sameSign(iv.Years, iv.Months) ||
   286  		!sameSign(iv.Hours, iv.Minutes) {
   287  		return false
   288  	}
   289  	// We allow large days and hours values, because they are within different parts.
   290  	if int32abs(iv.Months) > 12 ||
   291  		int32abs(iv.Minutes) > 60 ||
   292  		int32abs(iv.Seconds) > 60 ||
   293  		int32abs(iv.SubSecondNanos) > 1e9 {
   294  		return false
   295  	}
   296  	// TODO: We don't currently validate that each part represents value smaller than 10k years.
   297  	return true
   298  }
   299  
   300  func int32abs(x int32) int32 {
   301  	if x < 0 {
   302  		return -x
   303  	}
   304  	return x
   305  }
   306  
   307  func sameSign(nums ...int32) bool {
   308  	var pos, neg int
   309  	for _, n := range nums {
   310  		if n > 0 {
   311  			pos = pos + 1
   312  		}
   313  		if n < 0 {
   314  			neg = neg + 1
   315  		}
   316  	}
   317  	if pos > 0 && neg > 0 {
   318  		return false
   319  	}
   320  	return true
   321  }
   322  

View as plain text