github.com/go-chrono/chrono@v0.0.0-20240102183611-532f0d0d7c34/format.go (about) 1 package chrono 2 3 import ( 4 "fmt" 5 "strconv" 6 "strings" 7 ) 8 9 // These are predefined layouts used for the parsing and formatting of dates, times and date-times. 10 // Additional layouts can be composed using the specifiers detailed below: 11 // 12 // - %Y: The ISO 8601 year as a decimal number, padded to 4 digits with leading 0s. 13 // - %EY: The year in the era as a decimal number, padded to 4 digits with leading 0s. 14 // - %y: The ISO 8601 year without a century as a decimal number, padded to 2 digits with a leading 0, in the range 00 to 99. See note (1). 15 // - %Ey: The year in the era without a century as a decimal number, padded to 2 digits with a leading 0, in the range 00 to 99. See notes (1) and (9). 16 // - %C: The century as a decimal number, padded to 2 digits with a leading 0, e.g. 19 for 1980. See note (9). 17 // - %EC: The name of the era, either "CE" (for Common Era) "BCE" (for Before the Common Era). 18 // - %j: The day of the year as a decimal number, padded to 3 digits with leading 0s, in the range 001 to 366. See note (2). 19 // - %m: The month as a decimal number, padded to 2 digits with a leading 0, in the range 01 to 12. 20 // - %B: The full month name, e.g. January, February, etc. 21 // - %b: The abbreviated month name, e.g. Jan, Feb, etc. 22 // - %d: The day of the month as a decimal number, padded to 2 digits with a leading 0, in the range 01 to 31. 23 // 24 // Days of week: 25 // 26 // - %u: The day of the week as a decimal number, e.g. 1 for Monday, 2 for Tuesday, etc. See note (3). 27 // - %A: The full name of the day of the week, e.g. Monday, Tuesday, etc. See note (3). 28 // - %a: The abbreviated name of the day of the week, e.g. Mon, Tue, etc. See note (3). 29 // 30 // Week numbers: 31 // 32 // - %G: The ISO 8601 week-based year, padded to 4 digits with leading 0s. This may differ by ±1 to the actual calendar year. See note (2). 33 // - %V: The ISO week number, padded to 2 digits with a leading 0, in the range 01 to 53. See note (2). 34 // 35 // Times of day: 36 // 37 // - %P: Either "am" or "pm", where noon is "pm" and midnight is "am". 38 // - %p: Either "AM" or "PM", where noon is "PM" and midnight is "AM". 39 // - %I: The hour of the day using the 12-hour clock as a decimal number, padded to 2 digits with a leading 0, in the range 01 to 12. See note (4). 40 // 41 // Time components: 42 // 43 // - %H: The hour of the day using the 24-hour clock as a decimal number, padded to 2 digits with a leading 0, in the range 00 to 23. See note (5). 44 // - %M: The minute as a decimal number, padded to 2 digits with a leading 0, in the range 00 to 59. 45 // - %S: The second as a decimal number, padded to 2 digits with a leading 0, in the range 00 to 59. 46 // 47 // Millisecond precisions: 48 // 49 // - %f: Equivalent to %6f. 50 // - %3f: The millisecond offset within the represented second, rounded either up or down and padded to 3 digits with leading 0s. 51 // - %6f: The microsecond offset within the represented second, rounded either up or down and padded to 6 digits with leading 0s. 52 // - %9f: The nanosecond offset within the represented second, padded to 9 digits with leading 0s. 53 // 54 // Time offsets: 55 // 56 // - %z: The UTC offset in the format ±HHMM, preceded always by the sign ('+' or '-'), and padded to 4 digits with leading zeros. See notes (6), (7), and (8). 57 // - %Ez: Equivalent to %z, except that an offset of +0000 is formatted at 'Z', and other offsets as ±HH:MM. See notes (6) and (7). 58 // 59 // When formatting using specifiers that represent padded decimals, leading 0s can be omitted using the '-' character after the '%'. 60 // For example, '%m' may produce the string '04' (for March), but '%-m' produces '4'. 61 // However, when parsing using these specifiers, it is not required that the input string contains any leading zeros. 62 // 63 // When parsing using specifier that represent textual values (month names, etc.), the input text is treated case insensitively. 64 // 65 // Depending on the context in which the layout is used, only a subset of specifiers may be supported by a particular function. 66 // For example, %H is not supported when parsing or formatting a date. 67 // 68 // When parsing, if multiple instances of the same specifier, or multiple instances of a specifier that represent the same value, 69 // are encountered, only the instance will be considered. See note (2). 70 // 71 // If a specifier is encountered which is not recognized (defined in the list above), or not supported by a particular function, 72 // the function will panic with a message that includes the unrecognized sequence. 73 // 74 // Any other text is enchoed verbatim when formatting, and is expected to appear verbatim in the parsed text. 75 // In order to print the '%' character verbatim (which normally signifies a specifier), the sequence '%%' can be used. 76 // 77 // For familiarity, the examples below use the time package's reference time of "2nd Jan 2006 15:04:05 -0700" (Unix time 1136239445). 78 // But note that this reference format is not relevant at all to the functioning of this package. 79 // 80 // Notes: 81 // 82 // 1. When 2-digit years are parsed (%y or %Ey), they are converted according to the POSIX and ISO C standards: 83 // values 69–99 are mapped to 1969–1999, and values 0–68 are mapped to 2000–2068. 84 // 2. When a date is parsed in combination with a day of year (%j), and/or an ISO week-based date (%G and/or %V), 85 // an error will be returned if the represented dates to not match. 86 // 3. When a date is parsed in combination with a day of the week (%a, %A and/or %u), 87 // an error will be returned if it does not match the day represented by the parsed date. 88 // The day of the week is otherwise ignored - it does not have any effect on the result. 89 // 4. When a time represented in the 12-hour clock format (%I) is parsed, and no time of day (%P or %p) is present, 90 // the time of day is assumed to be before noon, i.e. am or AM. 91 // 5. When a time is parsed that contains the time of day (%P or %p), any hour (%H) that is present 92 // must be valid on the 12-hour clock. 93 // 6. When UTC offsets are parsed (%z or %Ez) into a type which do not include a time offset element, 94 // the offset present in the string is ignored. 95 // When UTC offsets are formatted from a type which does not include a time offset element, 96 // the offset will not be present in the returned string. 97 // 7. When UTC offsets are parsed (%z or %Ez), the shorted form of ±HH is accepted. 98 // However, when formatted, only the full forms are returned (either ±HHMM or ±HH:MM). 99 // 8. When %z is used for parsing a UTC offset, 'Z' can be used to represent an offset of +0000. 100 // 9. When parsing partial years (%Ey and %C) in combination with a full year (%Y or %EY), 101 // an error will be returned if the represented years to not match. 102 const ( 103 // ISO 8601. 104 ISO8601 = ISO8601DateTimeExtended 105 ISO8601DateSimple = "%Y%m%d" // 20060102 106 ISO8601DateExtended = "%Y-%m-%d" // 2006-01-02 107 ISO8601DateTruncated = "%Y-%m" // 2006-01 108 ISO8601TimeSimple = "T%H%M%S%z" // T030405-0700 109 ISO8601TimeExtended = "T%H:%M:%S%Ez" // T03:04:05-07:00 110 ISO8601TimeMillisSimple = "T%H%M%S.%3f%z" // T030405.000-0700 111 ISO8601TimeMillisExtended = "T%H:%M:%S.%3f%Ez" // T03:04:05.000-07:00 112 ISO8601TimeTruncatedMinsSimple = "T%H%M" // T0304 113 ISO8601TimeTruncatedMinsExtended = "T%H:%M" // T03:04 114 ISO8601TimeTruncatedHours = "T%H" // T03 115 ISO8601DateTimeSimple = ISO8601DateSimple + ISO8601TimeSimple // 20060102T030405-0700 116 ISO8601DateTimeExtended = ISO8601DateExtended + ISO8601TimeExtended // 2006-01-02T03:04:05-07:00 117 ISO8601WeekSimple = "%GW%V" // 2006W01 118 ISO8601WeekExtended = "%G-W%V" // 2006-W01 119 ISO8601WeekDaySimple = "%GW%V%u" // 2006W011 120 ISO8601WeekDayExtended = "%G-W%V-%u" // 2006-W01-1 121 ISO8601OrdinalDateSimple = "%Y%j" // 2006002 122 ISO8601OrdinalDateExtended = "%Y-%j" // 2006-002 123 // Layouts defined by the time package. 124 ANSIC = "%a %b %d %H:%M:%S %Y" // Mon Jan 02 15:04:05 2006 125 Kitchen = "%I:%M%p" // 3:04PM 126 ) 127 128 func formatDateTimeOffset(layout string, date *int32, time *int64, offset *int64) (string, error) { 129 var ( 130 year int 131 month int 132 day int 133 hour int 134 min int 135 sec int 136 ) 137 138 var err error 139 if date != nil { 140 v := int64(*date) 141 if year, month, day, err = fromDate(v); err != nil { 142 return "", err 143 } 144 } 145 146 if time != nil { 147 v := int64(*time) 148 hour, min, sec, _ = fromTime(v) 149 } 150 151 var buf, out []rune 152 NextChar: 153 for _, c := range layout { 154 buf = append(buf, c) 155 156 if len(buf) >= 2 && buf[0] == '%' { 157 if c == '-' || c == 'E' || (c >= '0' && c <= '9') { 158 continue NextChar 159 } 160 161 nopad, localed, precision, main, err := parseSpecifier(buf) 162 if err != nil { 163 return "", err 164 } 165 166 decimal := func(v int, len int) string { 167 if nopad { 168 return strconv.Itoa(v) 169 } 170 return fmt.Sprintf("%0*d", len, v) 171 } 172 173 switch { 174 case date != nil && main == 'a': // %a 175 out = append(out, []rune(shortWeekdayName(getWeekday(*date)))...) 176 case date != nil && main == 'A': // %A 177 out = append(out, []rune(longWeekdayName(getWeekday(*date)))...) 178 case date != nil && main == 'b': // %b 179 out = append(out, []rune(shortMonthName(month))...) 180 case date != nil && main == 'B': // %B 181 out = append(out, []rune(longMonthName(month))...) 182 case date != nil && main == 'C': 183 if localed { // %EC 184 if _, isBCE := convertISOToGregorianYear(year); isBCE { 185 out = append(out, []rune("BCE")...) 186 } else { 187 out = append(out, []rune("CE")...) 188 } 189 } else { // %C 190 out = append(out, []rune(fmt.Sprintf("%02d", year/100))...) 191 } 192 case date != nil && main == 'd': // %d 193 out = append(out, []rune(decimal(day, 2))...) 194 case time != nil && main == 'f': // %f 195 if precision == 0 { 196 precision = 6 197 } 198 199 nanos := timeNanoseconds(*time) 200 switch precision { 201 case 3: // %3f 202 out = append(out, []rune(decimal(divideAndRoundInt(nanos, 1000000), 3))...) 203 case 6: // %6f 204 out = append(out, []rune(decimal(divideAndRoundInt(nanos, 1000), 6))...) 205 case 9: // %9f 206 out = append(out, []rune(decimal(nanos, 9))...) 207 default: 208 panic(fmt.Sprintf("unsupported specifier '%df'", precision)) 209 } 210 case date != nil && main == 'G': // %G 211 v := int64(*date) 212 y, _, err := getISOWeek(v) 213 if err != nil { 214 panic(err.Error()) 215 } 216 out = append(out, []rune(decimal(y, 4))...) 217 case time != nil && main == 'H': // %H 218 out = append(out, []rune(decimal(hour, 2))...) 219 case time != nil && main == 'I': // %I 220 h, _ := convert24To12HourClock(hour) 221 out = append(out, []rune(decimal(h, 2))...) 222 case date != nil && main == 'j': // %j 223 v := int64(*date) 224 d, err := getYearDay(v) 225 if err != nil { 226 panic(err.Error()) 227 } 228 out = append(out, []rune(decimal(d, 3))...) 229 case date != nil && main == 'm': // %m 230 out = append(out, []rune(decimal(int(month), 2))...) 231 case time != nil && main == 'M': // %M 232 out = append(out, []rune(decimal(min, 2))...) 233 case time != nil && main == 'p': // %p 234 if _, isAfternoon := convert24To12HourClock(hour); !isAfternoon { 235 out = append(out, []rune("AM")...) 236 } else { 237 out = append(out, []rune("PM")...) 238 } 239 case time != nil && main == 'P': // %P 240 if _, isAfternoon := convert24To12HourClock(hour); !isAfternoon { 241 out = append(out, []rune("am")...) 242 } else { 243 out = append(out, []rune("pm")...) 244 } 245 case time != nil && main == 'S': // %S 246 out = append(out, []rune(decimal(sec, 2))...) 247 case date != nil && main == 'u': // %u 248 out = append(out, []rune(strconv.Itoa(getWeekday(*date)))...) 249 case date != nil && main == 'V': // %V 250 v := int64(*date) 251 _, w, err := getISOWeek(v) 252 if err != nil { 253 panic(err.Error()) 254 } 255 out = append(out, []rune(decimal(w, 2))...) 256 case date != nil && main == 'y': // %y 257 y := year 258 if localed { // %Ey 259 y, _ = convertISOToGregorianYear(y) 260 } 261 out = append(out, []rune(decimal(y%100, 2))...) 262 case date != nil && main == 'Y': // %Y 263 y := year 264 if localed { // %EY 265 y, _ = convertISOToGregorianYear(y) 266 } 267 out = append(out, []rune(decimal(y, 4))...) 268 case time != nil && main == 'z': 269 // Formatting %z from a type that contains no offset (e.g. LocalTime, LocalDateTime) 270 // is valid, although it will not be printed. 271 if offset == nil { 272 break 273 } 274 275 if localed { // %Ez 276 out = append(out, []rune(offsetString(*offset, ":"))...) 277 } else { // %z 278 out = append(out, []rune(offsetString(*offset, ""))...) 279 } 280 case main == '%': // %% 281 out = append(out, '%') 282 default: 283 return "", fmt.Errorf("unsupported sequence %q", string(buf)) 284 } 285 286 buf = nil 287 } else if len(buf) == 1 && buf[0] != '%' { 288 out = append(out, buf...) 289 buf = nil 290 } 291 } 292 293 return string(out), nil 294 } 295 296 var overrideCentury *int 297 298 func getCentury(year int) int { 299 switch { 300 case overrideCentury != nil: 301 return *overrideCentury 302 case year >= 69 && year <= 99: 303 return 1900 304 default: 305 return 2000 306 } 307 } 308 309 const ( 310 extraTextErrMsg = "parsing time \"%s\": extra text: \"%s\"" 311 endOfStringErrMsg = "parsing time \"%s\": end of string" 312 ) 313 314 // parseDateAndTime parses the supplied value according to the specified layout. 315 // date, time and offset must be provided in order for those components to be parsed. 316 // If not provided, and the specifiers that pertain to those components are 317 // encountered in the supplied layout, then an error is returned. 318 // If non-zero, date, time, and offset and taken as starting points, where the individual values 319 // that they represent are replaced only if present in the supplied layout. 320 func parseDateAndTime(layout, value string, date, time, offset *int64) error { 321 var ( 322 haveDate bool 323 haveGregorianYear bool 324 isBCE bool 325 year int 326 yearCentury *int 327 shortYear *int 328 yearType int // -1 = short/century, 0 = none, 1 = full year 329 330 month int 331 day int 332 333 dayOfWeek int 334 335 dayOfYear int 336 337 haveISODate bool 338 isoYear int 339 isoWeek int 340 341 have12HourClock bool 342 isAfternoon bool 343 hour int 344 min int 345 sec int 346 nsec int 347 ) 348 349 var err error 350 if date != nil { 351 if year, month, day, err = fromDate(*date); err != nil { 352 return err 353 } 354 355 if isoYear, isoWeek, err = getISOWeek(*date); err != nil { 356 return err 357 } 358 } 359 360 if time != nil { 361 hour, min, sec, nsec = fromTime(*time) 362 _, isAfternoon = convert24To12HourClock(hour) 363 } 364 365 var pos int 366 var buf []rune 367 for i := 0; i <= len(layout); i++ { 368 verifyText := func() error { 369 if len(buf) == 0 { 370 return nil 371 } 372 373 verify := func() error { 374 if !strings.HasPrefix(value[pos:], string(buf)) { 375 return fmt.Errorf("parsing time \"%s\" as \"%s\": cannot parse \"%s\" as \"%s\"", value, layout, value[pos:], string(buf)) 376 } 377 return nil 378 } 379 380 if buf[len(buf)-1] == '%' { 381 if err := verify(); err != nil { 382 return err 383 } 384 buf = []rune{'%'} 385 } else { 386 if err := verify(); err != nil { 387 return err 388 } 389 } 390 391 pos += len(buf) 392 buf = nil 393 return nil 394 } 395 396 processSpecifier := func() error { 397 integer := func(maxLen int) (int, error) { 398 var neg bool 399 400 str := value[pos:] 401 if len(str) >= 1 { 402 switch str[0] { 403 case '-': 404 neg = true 405 fallthrough 406 case '+': 407 str = str[1:] 408 pos++ 409 } 410 } 411 412 if l := len(str); l == 0 { 413 return 0, fmt.Errorf(endOfStringErrMsg, value) 414 } else if l < maxLen { 415 maxLen = l 416 } 417 str = str[:maxLen] 418 419 var i int 420 for _, char := range str { 421 if (char < '0' || char > '9') && char != '.' && char != ',' { 422 break 423 } 424 i++ 425 } 426 pos += i 427 428 if i == 0 { 429 return 0, fmt.Errorf(extraTextErrMsg, value, str) 430 } 431 432 out, err := strconv.Atoi(str[:i]) 433 if err != nil { 434 return 0, fmt.Errorf(extraTextErrMsg, value, str) 435 } 436 437 if neg { 438 return out * -1, nil 439 } 440 return out, nil 441 } 442 443 hasMore := func() bool { 444 return len(value[pos:]) > 0 445 } 446 447 casedAlpha := func(char rune) (rune, bool) { 448 str := value[pos:] 449 if len(str) != 0 { 450 r := rune(str[0]) 451 if r == char { 452 pos++ 453 return r, true 454 } 455 } 456 return ' ', false 457 } 458 459 alphas := func(maxLen int) (lower, original string) { 460 str := value[pos:] 461 462 if l := len(str); l < maxLen { 463 maxLen = l 464 } 465 str = value[pos : pos+maxLen] 466 467 _lower := make([]rune, maxLen) 468 _original := make([]rune, maxLen) 469 470 var i int 471 for _, char := range str { 472 if char >= 'a' && char <= 'z' { 473 _lower[i] = char 474 _original[i] = char 475 i++ 476 } else if char >= 'A' && char <= 'Z' { 477 _lower[i] = char + 32 478 _original[i] = char 479 i++ 480 } else { 481 break 482 } 483 } 484 pos += i 485 486 return string(_lower[:i]), string(_original[:i]) 487 } 488 489 _, localed, precision, main, err := parseSpecifier(buf) 490 if err != nil { 491 return err 492 } 493 494 switch { 495 case date != nil && main == 'a': // %a 496 lower, original := alphas(3) 497 var ok bool 498 if dayOfWeek, ok = shortDayNameLookup[lower]; !ok { 499 return fmt.Errorf("unrecognized short day name %q", original) 500 } 501 case date != nil && main == 'A': // %A 502 lower, original := alphas(9) 503 var ok bool 504 if dayOfWeek, ok = longDayNameLookup[lower]; !ok { 505 return fmt.Errorf("unrecognized day name %q", original) 506 } 507 case date != nil && main == 'b': // %b 508 lower, original := alphas(3) 509 var ok bool 510 if month, ok = shortMonthNameLookup[lower]; !ok { 511 return fmt.Errorf("unrecognized short month name %q", original) 512 } 513 case date != nil && main == 'B': // %B 514 lower, original := alphas(9) 515 var ok bool 516 if month, ok = longMonthNameLookup[lower]; !ok { 517 return fmt.Errorf("unrecognized month name %q", original) 518 } 519 case date != nil && main == 'C': 520 if localed { // %EC 521 haveGregorianYear = true 522 lower, original := alphas(3) 523 switch lower { 524 case "ce": 525 case "bce": 526 isBCE = true 527 default: 528 return fmt.Errorf("unrecognized era %q", original) 529 } 530 } else { // %C 531 var v int 532 if v, err = integer(2); err != nil { 533 return err 534 } 535 yearCentury = &v 536 yearType = -1 537 } 538 case date != nil && main == 'd': // %d 539 haveDate = true 540 if day, err = integer(2); err != nil { 541 return err 542 } 543 case time != nil && main == 'f': // %f 544 if precision == 0 { 545 precision = 6 546 } 547 548 switch precision { 549 case 3: // %3f 550 millis, err := integer(3) 551 if err != nil { 552 return err 553 } 554 nsec = millis * 1000000 555 case 6: // %6f 556 micros, err := integer(6) 557 if err != nil { 558 return err 559 } 560 nsec = micros * 1000 561 case 9: // %9f 562 if nsec, err = integer(9); err != nil { 563 return err 564 } 565 default: 566 } 567 case date != nil && main == 'G': // %G 568 haveISODate = true 569 if isoYear, err = integer(4); err != nil { 570 return err 571 } 572 case time != nil && main == 'H': // %H 573 if hour, err = integer(2); err != nil { 574 return err 575 } 576 case time != nil && main == 'I': // %I 577 have12HourClock = true 578 if hour, err = integer(2); err != nil { 579 return err 580 } 581 case date != nil && main == 'j': // %j 582 if dayOfYear, err = integer(3); err != nil { 583 return err 584 } 585 case date != nil && main == 'm': // %m 586 if month, err = integer(2); err != nil { 587 return err 588 } 589 case time != nil && main == 'M': // %M 590 if min, err = integer(2); err != nil { 591 return err 592 } 593 case time != nil && main == 'p': // %p 594 lower, original := alphas(2) 595 switch strings.ToUpper(lower) { 596 case "AM": 597 case "PM": 598 isAfternoon = true 599 default: 600 return fmt.Errorf("failed to parse time of day %q", original) 601 } 602 case time != nil && main == 'P': // %P 603 lower, original := alphas(2) 604 switch lower { 605 case "am": 606 case "pm": 607 isAfternoon = true 608 default: 609 return fmt.Errorf("failed to parse time of day %q", original) 610 } 611 case time != nil && main == 'S': // %S 612 if sec, err = integer(2); err != nil { 613 return err 614 } 615 case date != nil && main == 'u': // %u 616 if dayOfWeek, err = integer(1); err != nil { 617 return err 618 } 619 case date != nil && main == 'V': // %V 620 haveISODate = true 621 if isoWeek, err = integer(2); err != nil { 622 return err 623 } 624 case date != nil && main == 'y': // %y 625 if localed { // %Ey 626 haveGregorianYear = true 627 } 628 629 var v int 630 if v, err = integer(2); err != nil { 631 return err 632 } 633 shortYear = &v 634 yearType = -1 635 case date != nil && main == 'Y': // %Y 636 if localed { // %EY 637 haveGregorianYear = true 638 } 639 640 if year, err = integer(4); err != nil { 641 return err 642 } 643 yearType = 1 644 case time != nil && main == 'z': // %z 645 // If at end of input and no offset is requested, break. 646 // But continue to parse in the case where offset is not requested, but may be present. 647 if offset == nil && !hasMore() { 648 break 649 } 650 651 var v int64 652 var h, m int 653 var err error 654 655 // Catch the 'Z' case, which is valid for both %z and %Ez. 656 // Continue instead of breaking because offset may need updating. 657 if _, ok := casedAlpha('Z'); ok { 658 goto CalculateOffset 659 } 660 661 if h, err = integer(2); err != nil { 662 return err 663 } 664 665 if !hasMore() { 666 goto CalculateOffset 667 } 668 669 if localed { // %Ez 670 if actual, ok := casedAlpha(':'); !ok { 671 return fmt.Errorf(extraTextErrMsg, value, string(actual)) 672 } 673 } 674 675 if m, err = integer(2); err != nil { 676 return err 677 } 678 679 CalculateOffset: 680 if h >= 0 { 681 v = int64(h)*oneHour + int64(m)*oneMinute 682 } else { 683 v = int64(h)*oneHour - int64(m)*oneMinute 684 } 685 686 // Parsing %z into a type that contains no offset (e.g. LocalTime, LocalDateTime) 687 // is valid, although the value itself is ignored. But it needed to be consumed above, just now discarded. 688 if offset != nil { 689 *offset = v 690 } 691 case main == '%': // %% 692 default: 693 return fmt.Errorf("unsupported sequence %q", string(buf)) 694 } 695 696 buf = nil 697 return nil 698 } 699 700 // Some short-hands. 701 var ( 702 valid = i < len(layout) 703 isSpecifier = len(buf) >= 2 && buf[0] == '%' 704 specifierComplete = isSpecifier && (buf[len(buf)-1] != '-' && buf[len(buf)-1] != 'E' && (buf[len(buf)-1] < '0' || buf[len(buf)-1] > '9')) 705 isText = len(buf) >= 1 && buf[0] != '%' 706 ) 707 708 if valid { 709 c := layout[i] 710 if len(buf) == 0 { 711 goto AppendToBuffer 712 } else if isSpecifier { 713 if !specifierComplete { 714 goto AppendToBuffer 715 } 716 717 if err := processSpecifier(); err != nil { 718 return err 719 } 720 goto AppendToBuffer 721 } else if isText && c == '%' { 722 if err := verifyText(); err != nil { 723 return err 724 } 725 goto AppendToBuffer 726 } 727 728 AppendToBuffer: 729 buf = append(buf, rune(c)) 730 } else if isSpecifier { 731 if err := processSpecifier(); err != nil { 732 return err 733 } 734 } else if isText { 735 if err := verifyText(); err != nil { 736 return err 737 } 738 } 739 } 740 741 if pos < len(value) { 742 return fmt.Errorf(extraTextErrMsg, value, value[pos:]) 743 } 744 745 if date != nil { 746 // Check century according to note (9). 747 if yearCentury != nil { 748 if yearType == 1 && year/100 != *yearCentury { 749 return fmt.Errorf("year century %d does not agree with year %d", *yearCentury, year) 750 } else if yearType != 1 { 751 year = *yearCentury * 100 752 } 753 } 754 755 // Check 2-digit year according to note (9). 756 if shortYear != nil { 757 _year := getCentury(*shortYear) + *shortYear 758 if yearCentury != nil { 759 _year = *yearCentury*100 + *shortYear 760 } 761 762 if yearType == 1 && year-(year/100*100) != *shortYear { 763 return fmt.Errorf("short year %d (%d) does not agree with year %d", *shortYear, _year, year) 764 } else if yearType != 1 { 765 year = _year 766 } 767 } 768 769 if haveGregorianYear { 770 if year, err = convertGregorianToISOYear(year, isBCE); err != nil { 771 return err 772 } 773 } 774 775 if !isDateValid(year, month, day) { 776 return fmt.Errorf("invalid date %q", simpleDateStr(year, month, day)) 777 } 778 779 _date, err := makeDate(year, month, day) 780 if err != nil { 781 return err 782 } 783 784 *date = _date 785 786 // Check day of year according to note (2). 787 if dayOfYear != 0 { 788 doyDate, err := ofDayOfYear(year, dayOfYear) 789 if err != nil { 790 return err 791 } 792 793 if haveDate && (doyDate != _date) { 794 return fmt.Errorf("day-of-year date %q does not agree with date %q", 795 LocalDate(doyDate).String(), 796 simpleDateStr(year, month, day), 797 ) 798 } 799 800 *date = doyDate 801 } 802 803 // Check ISO week-year according to note (2). 804 if haveISODate { 805 weekday := dayOfWeek 806 if dayOfWeek == 0 { 807 weekday = int(Monday) 808 } 809 810 isoDate, err := ofISOWeek(isoYear, isoWeek, weekday) 811 if err != nil { 812 return fmt.Errorf("invalid ISO week-year date %q", getISODateSimpleStr(isoYear, isoWeek, day)) 813 } 814 815 if haveDate && (isoDate != _date) { 816 return fmt.Errorf("ISO week-year date %q does not agree with date %q", 817 getISODateSimpleStr(isoYear, isoWeek, day), 818 simpleDateStr(year, month, day), 819 ) 820 } 821 822 *date = isoDate 823 } 824 825 // Check day of week according to note (3). 826 haveDate = haveDate || dayOfYear != 0 827 if dayOfWeek != 0 && haveDate { 828 if actual := getWeekday(int32(*date)); dayOfWeek != actual { 829 return fmt.Errorf("day of week %q does not agree with actual day of week %q", 830 longWeekdayName(dayOfWeek), 831 longWeekdayName(actual), 832 ) 833 } 834 } 835 } 836 837 if time != nil { 838 // Check validity of hour on 12-hour clock according to note (5). 839 if have12HourClock { 840 if hour < 1 || hour > 12 { 841 return fmt.Errorf("hour %d is not valid on the 12-hour clock", hour) 842 } 843 hour = convert12To24HourClock(hour, isAfternoon) 844 } 845 846 v, err := makeTime(hour, min, sec, nsec) 847 if err != nil { 848 return err 849 } 850 *time = v 851 } 852 853 return nil 854 } 855 856 func parseSpecifier(buf []rune) (nopad, localed bool, precision uint, main rune, err error) { 857 if len(buf) == 3 { 858 switch { 859 case buf[1] == '-': 860 nopad = true 861 case buf[1] == 'E': 862 localed = true 863 case buf[1] >= '0' && buf[1] <= '9': 864 precision = uint(buf[1] - 48) 865 default: 866 return false, false, 0, 0, fmt.Errorf("unsupported modifier '%c'", buf[1]) 867 } 868 } else if len(buf) == 4 { 869 switch buf[1] { 870 case '-': 871 nopad = true 872 default: 873 return false, false, 0, 0, fmt.Errorf("unsupported modifier '%c'", buf[1]) 874 } 875 876 switch buf[2] { 877 case 'E': 878 localed = true 879 default: 880 return false, false, 0, 0, fmt.Errorf("unsupported modifier '%c'", buf[1]) 881 } 882 } 883 return nopad, localed, precision, buf[len(buf)-1], nil 884 } 885 886 func convert12To24HourClock(hour12 int, isAfternoon bool) (hour24 int) { 887 if isAfternoon && hour12 == 12 { 888 return 12 889 } else if isAfternoon { 890 return hour12 + 12 891 } else if hour12 == 12 { 892 return 0 893 } 894 return hour12 895 } 896 897 func convert24To12HourClock(hour24 int) (hour12 int, isAfternoon bool) { 898 if hour24 == 0 { 899 return 12, false 900 } else if hour24 == 12 { 901 return 12, true 902 } else if hour24 < 12 { 903 return hour24, false 904 } 905 return hour24 % 12, true 906 } 907 908 func convertGregorianToISOYear(gregorianYear int, isBCE bool) (isoYear int, err error) { 909 if gregorianYear == 0 { 910 return 0, fmt.Errorf("invalid Gregorian year %04d", gregorianYear) 911 } 912 913 if isBCE { 914 return (gregorianYear * -1) + 1, nil 915 } 916 return gregorianYear, nil 917 } 918 919 func convertISOToGregorianYear(isoYear int) (gregorianYear int, isBCE bool) { 920 if isoYear <= 0 { 921 return (isoYear * -1) + 1, true 922 } 923 return isoYear, false 924 } 925 926 func shortWeekdayName(d int) string { 927 if d < int(Monday) || d > int(Sunday) { 928 return fmt.Sprintf("%%!Weekday(%d)", d) 929 } 930 return shortDayNames[d-1] 931 } 932 933 var longDayNameLookup = map[string]int{ 934 "monday": int(Monday), 935 "tuesday": int(Tuesday), 936 "wednesday": int(Wednesday), 937 "thursday": int(Thursday), 938 "friday": int(Friday), 939 "saturday": int(Saturday), 940 "sunday": int(Sunday), 941 } 942 943 var shortDayNames = [7]string{ 944 Monday - 1: "Mon", 945 Tuesday - 1: "Tue", 946 Wednesday - 1: "Wed", 947 Thursday - 1: "Thu", 948 Friday - 1: "Fri", 949 Saturday - 1: "Sat", 950 Sunday - 1: "Sun", 951 } 952 953 var shortDayNameLookup = map[string]int{ 954 "mon": int(Monday), 955 "tue": int(Tuesday), 956 "wed": int(Wednesday), 957 "thu": int(Thursday), 958 "fri": int(Friday), 959 "sat": int(Saturday), 960 "sun": int(Sunday), 961 } 962 963 func (m Month) short() string { 964 return shortMonthName(int(m)) 965 } 966 967 func shortMonthName(m int) string { 968 if m < int(January) || m > int(December) { 969 return fmt.Sprintf("%%!Month(%d)", m) 970 } 971 return shortMonthNames[m-1] 972 } 973 974 var longMonthNameLookup = map[string]int{ 975 "january": int(January), 976 "february": int(February), 977 "march": int(March), 978 "april": int(April), 979 "may": int(May), 980 "june": int(June), 981 "july": int(July), 982 "august": int(August), 983 "september": int(September), 984 "october": int(October), 985 "november": int(November), 986 "december": int(December), 987 } 988 989 var shortMonthNames = [12]string{ 990 January - 1: "Jan", 991 February - 1: "Feb", 992 March - 1: "Mar", 993 April - 1: "Apr", 994 May - 1: "May", 995 June - 1: "Jun", 996 July - 1: "Jul", 997 August - 1: "Aug", 998 September - 1: "Sep", 999 October - 1: "Oct", 1000 November - 1: "Nov", 1001 December - 1: "Dec", 1002 } 1003 1004 var shortMonthNameLookup = map[string]int{ 1005 "jan": int(January), 1006 "feb": int(February), 1007 "mar": int(March), 1008 "apr": int(April), 1009 "may": int(May), 1010 "jun": int(June), 1011 "jul": int(July), 1012 "aug": int(August), 1013 "sep": int(September), 1014 "oct": int(October), 1015 "nov": int(November), 1016 "dec": int(December), 1017 }