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  }