github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/sem/tree/interval.go (about) 1 // Copyright 2016 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package tree 12 13 import ( 14 "math" 15 "strconv" 16 "strings" 17 "time" 18 19 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" 20 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" 21 "github.com/cockroachdb/cockroach/pkg/sql/types" 22 "github.com/cockroachdb/cockroach/pkg/util/duration" 23 "github.com/cockroachdb/errors" 24 ) 25 26 type intervalLexer struct { 27 str string 28 offset int 29 err error 30 } 31 32 // consumeNum consumes the next decimal number. 33 // 1st return value is the integer part. 34 // 2nd return value is whether a decimal part was encountered. 35 // 3rd return value is the decimal part as a float. 36 // If the number is negative, both the 1st and 3rd return 37 // value are negative. 38 // The decimal value is returned separately from the integer value so 39 // as to support large integer values which would not fit in a float. 40 func (l *intervalLexer) consumeNum() (int64, bool, float64) { 41 if l.err != nil { 42 return 0, false, 0 43 } 44 45 offset := l.offset 46 47 neg := false 48 if l.offset < len(l.str) && l.str[l.offset] == '-' { 49 // Remember a leading negative sign. We can't use "intPart < 0" 50 // below, because when the input syntax is "-0.xxxx" intPart is 0. 51 neg = true 52 } 53 54 // Integer part before the decimal separator. 55 intPart := l.consumeInt() 56 57 var decPart float64 58 hasDecimal := false 59 if l.offset < len(l.str) && l.str[l.offset] == '.' { 60 hasDecimal = true 61 start := l.offset 62 63 // Advance offset to prepare a valid argument to ParseFloat(). 64 l.offset++ 65 for ; l.offset < len(l.str) && l.str[l.offset] >= '0' && l.str[l.offset] <= '9'; l.offset++ { 66 } 67 // Try to convert. 68 value, err := strconv.ParseFloat(l.str[start:l.offset], 64) 69 if err != nil { 70 l.err = pgerror.Newf( 71 pgcode.InvalidDatetimeFormat, "interval: %v", err) 72 return 0, false, 0 73 } 74 decPart = value 75 } 76 77 // Ensure we have something. 78 if offset == l.offset { 79 l.err = pgerror.Newf( 80 pgcode.InvalidDatetimeFormat, "interval: missing number at position %d: %q", offset, l.str) 81 return 0, false, 0 82 } 83 84 if neg { 85 decPart = -decPart 86 } 87 return intPart, hasDecimal, decPart 88 } 89 90 // Consumes the next integer. 91 func (l *intervalLexer) consumeInt() int64 { 92 if l.err != nil { 93 return 0 94 } 95 96 start := l.offset 97 98 // Advance offset to prepare a valid argument to ParseInt(). 99 if l.offset < len(l.str) && (l.str[l.offset] == '-' || l.str[l.offset] == '+') { 100 l.offset++ 101 } 102 for ; l.offset < len(l.str) && l.str[l.offset] >= '0' && l.str[l.offset] <= '9'; l.offset++ { 103 } 104 // Check if we have something like ".X". 105 if start == l.offset && len(l.str) > (l.offset+1) && l.str[l.offset] == '.' { 106 return 0 107 } 108 109 x, err := strconv.ParseInt(l.str[start:l.offset], 10, 64) 110 if err != nil { 111 l.err = pgerror.Newf( 112 pgcode.InvalidDatetimeFormat, "interval: %v", err) 113 return 0 114 } 115 if start == l.offset { 116 l.err = pgerror.Newf( 117 pgcode.InvalidDatetimeFormat, "interval: missing number at position %d: %q", start, l.str) 118 return 0 119 } 120 return x 121 } 122 123 // Consumes the next unit. 124 func (l *intervalLexer) consumeUnit(skipCharacter byte) string { 125 if l.err != nil { 126 return "" 127 } 128 129 offset := l.offset 130 for ; l.offset < len(l.str); l.offset++ { 131 if (l.str[l.offset] >= '0' && l.str[l.offset] <= '9') || 132 l.str[l.offset] == skipCharacter || 133 l.str[l.offset] == '-' { 134 break 135 } 136 } 137 138 if offset == l.offset { 139 l.err = pgerror.Newf( 140 pgcode.InvalidDatetimeFormat, "interval: missing unit at position %d: %q", offset, l.str) 141 return "" 142 } 143 return l.str[offset:l.offset] 144 } 145 146 // Consumes any number of spaces. 147 func (l *intervalLexer) consumeSpaces() { 148 if l.err != nil { 149 return 150 } 151 for ; l.offset < len(l.str) && l.str[l.offset] == ' '; l.offset++ { 152 } 153 } 154 155 // ISO Units. 156 var isoDateUnitMap = map[string]duration.Duration{ 157 "D": duration.MakeDuration(0, 1, 0), 158 "W": duration.MakeDuration(0, 7, 0), 159 "M": duration.MakeDuration(0, 0, 1), 160 "Y": duration.MakeDuration(0, 0, 12), 161 } 162 163 var isoTimeUnitMap = map[string]duration.Duration{ 164 "S": duration.MakeDuration(time.Second.Nanoseconds(), 0, 0), 165 "M": duration.MakeDuration(time.Minute.Nanoseconds(), 0, 0), 166 "H": duration.MakeDuration(time.Hour.Nanoseconds(), 0, 0), 167 } 168 169 const errInvalidSQLDuration = "invalid input syntax for type interval %s" 170 171 type parsedIndex uint8 172 173 const ( 174 nothingParsed parsedIndex = iota 175 hmsParsed 176 dayParsed 177 yearMonthParsed 178 ) 179 180 func newInvalidSQLDurationError(s string) error { 181 return pgerror.Newf(pgcode.InvalidDatetimeFormat, errInvalidSQLDuration, s) 182 } 183 184 // Parses a SQL standard interval string. 185 // See the following links for examples: 186 // - http://www.postgresql.org/docs/9.1/static/datatype-datetime.html#DATATYPE-INTERVAL-INPUT-EXAMPLES 187 // - http://www.ibm.com/support/knowledgecenter/SSGU8G_12.1.0/com.ibm.esqlc.doc/ids_esqlc_0190.htm 188 func sqlStdToDuration(s string, itm types.IntervalTypeMetadata) (duration.Duration, error) { 189 var d duration.Duration 190 parts := strings.Fields(s) 191 if len(parts) > 3 || len(parts) == 0 { 192 return d, newInvalidSQLDurationError(s) 193 } 194 // Index of which part(s) have been parsed for detecting bad order such as `HH:MM:SS Year-Month`. 195 parsedIdx := nothingParsed 196 // Both 'Day' and 'Second' can be float, but 'Day Second'::interval is invalid. 197 floatParsed := false 198 // Parsing backward makes it easy to distinguish 'Day' and 'Second' when encountering a single value. 199 // `1-2 5 9:` and `1-2 5` 200 // | | 201 // day ---+ | 202 // second ---------------+ 203 for i := len(parts) - 1; i >= 0; i-- { 204 // Parses leading sign 205 part := parts[i] 206 207 consumeNeg := func(str string) (newStr string, mult int64, ok bool) { 208 neg := false 209 // Consumes [-+] 210 if str != "" { 211 c := str[0] 212 if c == '-' || c == '+' { 213 neg = c == '-' 214 str = str[1:] 215 } 216 } 217 if len(str) == 0 { 218 return str, 0, false 219 } 220 if str[0] == '-' || str[0] == '+' { 221 return str, 0, false 222 } 223 224 mult = 1 225 if neg { 226 mult = -1 227 } 228 return str, mult, true 229 } 230 231 var mult int64 232 var ok bool 233 if part, mult, ok = consumeNeg(part); !ok { 234 return d, newInvalidSQLDurationError(s) 235 } 236 237 if strings.ContainsRune(part, ':') { 238 // Try to parse as HH:MM:SS 239 if parsedIdx != nothingParsed { 240 return d, newInvalidSQLDurationError(s) 241 } 242 parsedIdx = hmsParsed 243 // Colon-separated intervals in Postgres are odd. They have day, hour, 244 // minute, or second parts depending on number of fields and if the field 245 // is an int or float. 246 // 247 // Instead of supporting unit changing based on int or float, use the 248 // following rules: 249 // - If there is a float at the front, it represents D:(<apply below rules). 250 // - Two fields is H:M or M:S.fff (unless using MINUTE TO SECOND, then M:S). 251 // - Three fields is H:M:S(.fff)?. 252 hms := strings.Split(part, ":") 253 254 // If the first element is blank or is a float, it represents a day. 255 // Take it to days, and simplify logic below to a H:M:S scenario. 256 firstComponentIsFloat := strings.Contains(hms[0], ".") 257 if firstComponentIsFloat || hms[0] == "" { 258 // Negatives are not permitted in this format. 259 // Also, there must be more units in front. 260 if mult != 1 || len(hms) == 1 { 261 return d, newInvalidSQLDurationError(s) 262 } 263 if firstComponentIsFloat { 264 days, err := strconv.ParseFloat(hms[0], 64) 265 if err != nil { 266 return d, newInvalidSQLDurationError(s) 267 } 268 d = d.Add(duration.MakeDuration(0, 1, 0).MulFloat(days)) 269 } 270 271 hms = hms[1:] 272 if hms[0], mult, ok = consumeNeg(hms[0]); !ok { 273 return d, newInvalidSQLDurationError(s) 274 } 275 } 276 277 // Postgres fills in the blanks of all H:M:S as if they were zero. 278 for i := 0; i < len(hms); i++ { 279 if hms[i] == "" { 280 hms[i] = "0" 281 } 282 } 283 284 var hours, mins int64 285 var secs float64 286 287 switch len(hms) { 288 case 2: 289 // If we find a decimal, it must be the m:s.ffffff format 290 var err error 291 if strings.Contains(hms[1], ".") || itm.DurationField.IsMinuteToSecond() { 292 if mins, err = strconv.ParseInt(hms[0], 10, 64); err != nil { 293 return d, newInvalidSQLDurationError(s) 294 } 295 if secs, err = strconv.ParseFloat(hms[1], 64); err != nil { 296 return d, newInvalidSQLDurationError(s) 297 } 298 } else { 299 if hours, err = strconv.ParseInt(hms[0], 10, 64); err != nil { 300 return d, newInvalidSQLDurationError(s) 301 } 302 if mins, err = strconv.ParseInt(hms[1], 10, 64); err != nil { 303 return d, newInvalidSQLDurationError(s) 304 } 305 } 306 case 3: 307 var err error 308 if hours, err = strconv.ParseInt(hms[0], 10, 64); err != nil { 309 return d, newInvalidSQLDurationError(s) 310 } 311 if mins, err = strconv.ParseInt(hms[1], 10, 64); err != nil { 312 return d, newInvalidSQLDurationError(s) 313 } 314 if secs, err = strconv.ParseFloat(hms[2], 64); err != nil { 315 return d, newInvalidSQLDurationError(s) 316 } 317 default: 318 return d, newInvalidSQLDurationError(s) 319 } 320 321 // None of these units can be negative, as we explicitly strip the negative 322 // unit from the very beginning. 323 if hours < 0 || mins < 0 || secs < 0 { 324 return d, newInvalidSQLDurationError(s) 325 } 326 327 d = d.Add(duration.MakeDuration(time.Hour.Nanoseconds(), 0, 0).Mul(mult * hours)) 328 d = d.Add(duration.MakeDuration(time.Minute.Nanoseconds(), 0, 0).Mul(mult * mins)) 329 d = d.Add(duration.MakeDuration(time.Second.Nanoseconds(), 0, 0).MulFloat(float64(mult) * secs)) 330 } else if strings.ContainsRune(part, '-') { 331 // Try to parse as Year-Month. 332 if parsedIdx >= yearMonthParsed { 333 return d, newInvalidSQLDurationError(s) 334 } 335 parsedIdx = yearMonthParsed 336 337 yms := strings.Split(part, "-") 338 if len(yms) != 2 { 339 return d, newInvalidSQLDurationError(s) 340 } 341 year, errYear := strconv.Atoi(yms[0]) 342 var month int 343 var errMonth error 344 if yms[1] != "" { 345 // postgres technically supports decimals here, but it seems to be buggy 346 // due to the way it is parsed on their side. 347 // e.g. `select interval '0-2.1'` is different to select interval `'0-2.1 01:00'` -- 348 // it seems the ".1" represents either a day or a constant, which we cannot 349 // replicate because we use spaces for divisors, but also seems like something 350 // we shouldn't sink too much time into looking at supporting. 351 month, errMonth = strconv.Atoi(yms[1]) 352 } 353 if errYear == nil && errMonth == nil { 354 delta := duration.MakeDuration(0, 0, 1).Mul(int64(year)*12 + int64(month)) 355 if mult < 0 { 356 d = d.Sub(delta) 357 } else { 358 d = d.Add(delta) 359 } 360 } else { 361 return d, newInvalidSQLDurationError(s) 362 } 363 } else if value, err := strconv.ParseFloat(part, 64); err == nil { 364 // We cannot specify '<Day> <Second>'::interval as two floats, 365 // but we can in the DAY TO HOUR format, where it is '<Day> <Hour>'. 366 if floatParsed && !itm.DurationField.IsDayToHour() { 367 return d, newInvalidSQLDurationError(s) 368 } 369 floatParsed = true 370 if parsedIdx == nothingParsed { 371 // It must be <DurationType> part because nothing has been parsed. 372 switch itm.DurationField.DurationType { 373 case types.IntervalDurationType_YEAR: 374 d = d.Add(duration.MakeDuration(0, 0, 12).MulFloat(value * float64(mult))) 375 case types.IntervalDurationType_MONTH: 376 d = d.Add(duration.MakeDuration(0, 0, 1).MulFloat(value * float64(mult))) 377 case types.IntervalDurationType_DAY: 378 d = d.Add(duration.MakeDuration(0, 1, 0).MulFloat(value * float64(mult))) 379 case types.IntervalDurationType_HOUR: 380 d = d.Add(duration.MakeDuration(time.Hour.Nanoseconds(), 0, 0).MulFloat(value * float64(mult))) 381 case types.IntervalDurationType_MINUTE: 382 d = d.Add(duration.MakeDuration(time.Minute.Nanoseconds(), 0, 0).MulFloat(value * float64(mult))) 383 case types.IntervalDurationType_SECOND, types.IntervalDurationType_UNSET: 384 d = d.Add(duration.MakeDuration(time.Second.Nanoseconds(), 0, 0).MulFloat(value * float64(mult))) 385 case types.IntervalDurationType_MILLISECOND: 386 d = d.Add(duration.MakeDuration(time.Millisecond.Nanoseconds(), 0, 0).MulFloat(value * float64(mult))) 387 default: 388 return d, errors.AssertionFailedf("unhandled DurationField constant %#v", itm.DurationField) 389 } 390 parsedIdx = hmsParsed 391 } else if parsedIdx == hmsParsed { 392 // Day part. 393 delta := duration.MakeDuration(0, 1, 0).MulFloat(value) 394 if mult < 0 { 395 d = d.Sub(delta) 396 } else { 397 d = d.Add(delta) 398 } 399 parsedIdx = dayParsed 400 } else { 401 return d, newInvalidSQLDurationError(s) 402 } 403 } else { 404 return d, newInvalidSQLDurationError(s) 405 } 406 } 407 return d, nil 408 } 409 410 // Parses an ISO8601 (with designators) string. 411 // See the following links for examples: 412 // - http://www.postgresql.org/docs/9.1/static/datatype-datetime.html#DATATYPE-INTERVAL-INPUT-EXAMPLES 413 // - https://en.wikipedia.org/wiki/ISO_8601#Time_intervals 414 // - https://en.wikipedia.org/wiki/ISO_8601#Durations 415 func iso8601ToDuration(s string) (duration.Duration, error) { 416 var d duration.Duration 417 if len(s) == 0 || s[0] != 'P' { 418 return d, newInvalidSQLDurationError(s) 419 } 420 421 // Advance to offset 1, since we don't care about the leading P. 422 l := intervalLexer{str: s, offset: 1, err: nil} 423 unitMap := isoDateUnitMap 424 425 for l.offset < len(s) { 426 // Check if we're in the time part yet. 427 if s[l.offset] == 'T' { 428 unitMap = isoTimeUnitMap 429 l.offset++ 430 } 431 432 v := l.consumeInt() 433 u := l.consumeUnit('T') 434 if l.err != nil { 435 return d, l.err 436 } 437 438 if unit, ok := unitMap[u]; ok { 439 d = d.Add(unit.Mul(v)) 440 } else { 441 return d, pgerror.Newf( 442 pgcode.InvalidDatetimeFormat, 443 "interval: unknown unit %s in ISO-8601 duration %s", u, s) 444 } 445 } 446 447 return d, nil 448 } 449 450 // unitMap defines for each unit name what is the time duration for 451 // that unit. 452 var unitMap = func( 453 units map[string]duration.Duration, 454 aliases map[string][]string, 455 ) map[string]duration.Duration { 456 for a, alist := range aliases { 457 // Pluralize. 458 units[a+"s"] = units[a] 459 for _, alias := range alist { 460 // Populate the remaining aliases. 461 units[alias] = units[a] 462 } 463 } 464 return units 465 }(map[string]duration.Duration{ 466 // Use DecodeDuration here because ns is the only unit for which we do not 467 // want to round nanoseconds since it is only used for multiplication. 468 "microsecond": duration.MakeDuration(time.Microsecond.Nanoseconds(), 0, 0), 469 "millisecond": duration.MakeDuration(time.Millisecond.Nanoseconds(), 0, 0), 470 "second": duration.MakeDuration(time.Second.Nanoseconds(), 0, 0), 471 "minute": duration.MakeDuration(time.Minute.Nanoseconds(), 0, 0), 472 "hour": duration.MakeDuration(time.Hour.Nanoseconds(), 0, 0), 473 "day": duration.MakeDuration(0, 1, 0), 474 "week": duration.MakeDuration(0, 7, 0), 475 "month": duration.MakeDuration(0, 0, 1), 476 "year": duration.MakeDuration(0, 0, 12), 477 }, map[string][]string{ 478 // Include PostgreSQL's unit keywords for compatibility; see 479 // https://github.com/postgres/postgres/blob/a01d0fa1d889cc2003e1941e8b98707c4d701ba9/src/backend/utils/adt/datetime.c#L175-L240 480 // 481 // µ = U+00B5 = micro symbol 482 // μ = U+03BC = Greek letter mu 483 "microsecond": {"us", "µs", "μs", "usec", "usecs", "usecond", "useconds"}, 484 "millisecond": {"ms", "msec", "msecs", "msecond", "mseconds"}, 485 "second": {"s", "sec", "secs"}, 486 "minute": {"m", "min", "mins"}, 487 "hour": {"h", "hr", "hrs"}, 488 "day": {"d"}, 489 "week": {"w"}, 490 "month": {"mon", "mons"}, 491 "year": {"y", "yr", "yrs"}, 492 }) 493 494 // parseDuration parses a duration in the "traditional" Postgres 495 // format (e.g. '1 day 2 hours', '1 day 03:02:04', etc.) or golang 496 // format (e.g. '1d2h', '1d3h2m4s', etc.) 497 func parseDuration(s string, itm types.IntervalTypeMetadata) (duration.Duration, error) { 498 var d duration.Duration 499 l := intervalLexer{str: s, offset: 0, err: nil} 500 l.consumeSpaces() 501 502 if l.offset == len(l.str) { 503 return d, pgerror.Newf( 504 pgcode.InvalidDatetimeFormat, "interval: invalid input syntax: %q", l.str) 505 } 506 for l.offset != len(l.str) { 507 // To support -00:XX:XX we record the sign here since -0 doesn't exist 508 // as an int64. 509 sign := l.str[l.offset] == '-' 510 // Parse the next number. 511 v, hasDecimal, vp := l.consumeNum() 512 l.consumeSpaces() 513 514 if l.offset < len(l.str) && l.str[l.offset] == ':' && !hasDecimal { 515 // Special case: HH:MM[:SS.ffff] or MM:SS.ffff 516 delta, err := l.parseShortDuration(v, sign, itm) 517 if err != nil { 518 return d, err 519 } 520 d = d.Add(delta) 521 continue 522 } 523 524 // Parse the unit. 525 u := l.consumeUnit(' ') 526 l.consumeSpaces() 527 if unit, ok := unitMap[strings.ToLower(u)]; ok { 528 // A regular number followed by a unit, such as "9 day". 529 d = d.Add(unit.Mul(v)) 530 if hasDecimal { 531 d = addFrac(d, unit, vp) 532 } 533 continue 534 } 535 536 if u != "" { 537 return d, pgerror.Newf( 538 pgcode.InvalidDatetimeFormat, "interval: unknown unit %q in duration %q", u, s) 539 } 540 return d, pgerror.Newf( 541 pgcode.InvalidDatetimeFormat, "interval: missing unit at position %d: %q", l.offset, s) 542 } 543 return d, l.err 544 } 545 546 func (l *intervalLexer) parseShortDuration( 547 h int64, hasSign bool, itm types.IntervalTypeMetadata, 548 ) (duration.Duration, error) { 549 sign := int64(1) 550 if hasSign { 551 sign = -1 552 } 553 // postgresToDuration() has rewound the cursor to just after the 554 // first number, so that we can check here there are no unwanted 555 // spaces. 556 if l.str[l.offset] != ':' { 557 return duration.Duration{}, pgerror.Newf( 558 pgcode.InvalidDatetimeFormat, "interval: invalid format %s", l.str[l.offset:]) 559 } 560 l.offset++ 561 // Parse the second number. 562 m, hasDecimal, mp := l.consumeNum() 563 564 if m < 0 { 565 return duration.Duration{}, pgerror.Newf( 566 pgcode.InvalidDatetimeFormat, "interval: invalid format: %s", l.str) 567 } 568 // We have three possible formats: 569 // - MM:SS.ffffff 570 // - HH:MM (or MM:SS for MINUTE TO SECOND) 571 // - HH:MM:SS[.ffffff] 572 // 573 // The top format has the "h" field parsed above actually 574 // represent minutes. Get this out of the way first. 575 if hasDecimal { 576 l.consumeSpaces() 577 return duration.MakeDuration( 578 h*time.Minute.Nanoseconds()+ 579 sign*(m*time.Second.Nanoseconds()+ 580 floatToNanos(mp)), 581 0, 582 0, 583 ), nil 584 } 585 586 // Remaining formats. 587 var s int64 588 var sp float64 589 hasSecondsComponent := false 590 if l.offset != len(l.str) && l.str[l.offset] == ':' { 591 hasSecondsComponent = true 592 // The last :NN part. 593 l.offset++ 594 s, _, sp = l.consumeNum() 595 if s < 0 { 596 return duration.Duration{}, pgerror.Newf( 597 pgcode.InvalidDatetimeFormat, "interval: invalid format: %s", l.str) 598 } 599 } 600 601 l.consumeSpaces() 602 603 if !hasSecondsComponent && itm.DurationField.IsMinuteToSecond() { 604 return duration.MakeDuration( 605 h*time.Minute.Nanoseconds()+sign*(m*time.Second.Nanoseconds()), 606 0, 607 0, 608 ), nil 609 } 610 return duration.MakeDuration( 611 h*time.Hour.Nanoseconds()+ 612 sign*(m*time.Minute.Nanoseconds()+ 613 int64(mp*float64(time.Minute.Nanoseconds()))+ 614 s*time.Second.Nanoseconds()+ 615 floatToNanos(sp)), 616 0, 617 0, 618 ), nil 619 } 620 621 // addFrac increases the duration given as first argument by the unit 622 // given as second argument multiplied by the factor in the third 623 // argument. For computing fractions there are 30 days to a month and 624 // 24 hours to a day. 625 func addFrac(d duration.Duration, unit duration.Duration, f float64) duration.Duration { 626 if unit.Months > 0 { 627 f = f * float64(unit.Months) 628 d.Months += int64(f) 629 f = math.Mod(f, 1) * 30 630 d.Days += int64(f) 631 f = math.Mod(f, 1) * 24 632 d.SetNanos(d.Nanos() + int64(float64(time.Hour.Nanoseconds())*f)) 633 } else if unit.Days > 0 { 634 f = f * float64(unit.Days) 635 d.Days += int64(f) 636 f = math.Mod(f, 1) * 24 637 d.SetNanos(d.Nanos() + int64(float64(time.Hour.Nanoseconds())*f)) 638 } else { 639 d.SetNanos(d.Nanos() + int64(float64(unit.Nanos())*f)) 640 } 641 return d 642 } 643 644 // floatToNanos converts a fractional number representing nanoseconds to the 645 // number of integer nanoseconds. For example: ".354874219" to "354874219" 646 // or ".123" to "123000000". This function takes care to round correctly 647 // when a naive conversion would incorrectly truncate due to floating point 648 // inaccuracies. This function should match the semantics of rint() from 649 // Postgres. See: 650 // https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/adt/timestamp.c;h=449164ae7e5b00f6580771017888d4922685a73c;hb=HEAD#l1511 651 // https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/port/rint.c;h=d59d9ab774307b7db2f7cb2347815a30da563fc5;hb=HEAD 652 func floatToNanos(f float64) int64 { 653 return int64(math.Round(f * float64(time.Second.Nanoseconds()))) 654 }