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