Source file src/time/format_rfc3339.go

     1  // Copyright 2022 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package time
     6  
     7  import "errors"
     8  
     9  // RFC 3339 is the most commonly used format.
    10  //
    11  // It is implicitly used by the Time.(Marshal|Unmarshal)(Text|JSON) methods.
    12  // Also, according to analysis on https://go.dev/issue/52746,
    13  // RFC 3339 accounts for 57% of all explicitly specified time formats,
    14  // with the second most popular format only being used 8% of the time.
    15  // The overwhelming use of RFC 3339 compared to all other formats justifies
    16  // the addition of logic to optimize formatting and parsing.
    17  
    18  func (t Time) appendFormatRFC3339(b []byte, nanos bool) []byte {
    19  	_, offset, abs := t.locabs()
    20  
    21  	// Format date.
    22  	year, month, day := abs.days().date()
    23  	b = appendInt(b, year, 4)
    24  	b = append(b, '-')
    25  	b = appendInt(b, int(month), 2)
    26  	b = append(b, '-')
    27  	b = appendInt(b, day, 2)
    28  
    29  	b = append(b, 'T')
    30  
    31  	// Format time.
    32  	hour, min, sec := abs.clock()
    33  	b = appendInt(b, hour, 2)
    34  	b = append(b, ':')
    35  	b = appendInt(b, min, 2)
    36  	b = append(b, ':')
    37  	b = appendInt(b, sec, 2)
    38  
    39  	if nanos {
    40  		std := stdFracSecond(stdFracSecond9, 9, '.')
    41  		b = appendNano(b, t.Nanosecond(), std)
    42  	}
    43  
    44  	if offset == 0 {
    45  		return append(b, 'Z')
    46  	}
    47  
    48  	// Format zone.
    49  	zone := offset / 60 // convert to minutes
    50  	if zone < 0 {
    51  		b = append(b, '-')
    52  		zone = -zone
    53  	} else {
    54  		b = append(b, '+')
    55  	}
    56  	b = appendInt(b, zone/60, 2)
    57  	b = append(b, ':')
    58  	b = appendInt(b, zone%60, 2)
    59  	return b
    60  }
    61  
    62  func (t Time) appendStrictRFC3339(b []byte) ([]byte, error) {
    63  	n0 := len(b)
    64  	b = t.appendFormatRFC3339(b, true)
    65  
    66  	// Not all valid Go timestamps can be serialized as valid RFC 3339.
    67  	// Explicitly check for these edge cases.
    68  	// See https://go.dev/issue/4556 and https://go.dev/issue/54580.
    69  	num2 := func(b []byte) byte { return 10*(b[0]-'0') + (b[1] - '0') }
    70  	switch {
    71  	case b[n0+len("9999")] != '-': // year must be exactly 4 digits wide
    72  		return b, errors.New("year outside of range [0,9999]")
    73  	case b[len(b)-1] != 'Z':
    74  		c := b[len(b)-len("Z07:00")]
    75  		if ('0' <= c && c <= '9') || num2(b[len(b)-len("07:00"):]) >= 24 {
    76  			return b, errors.New("timezone hour outside of range [0,23]")
    77  		}
    78  	}
    79  	return b, nil
    80  }
    81  
    82  func parseRFC3339[bytes []byte | string](s bytes, local *Location) (Time, bool) {
    83  	// parseUint parses s as an unsigned decimal integer and
    84  	// verifies that it is within some range.
    85  	// If it is invalid or out-of-range,
    86  	// it sets ok to false and returns the min value.
    87  	ok := true
    88  	parseUint := func(s bytes, min, max int) (x int) {
    89  		for _, c := range []byte(s) {
    90  			if c < '0' || '9' < c {
    91  				ok = false
    92  				return min
    93  			}
    94  			x = x*10 + int(c) - '0'
    95  		}
    96  		if x < min || max < x {
    97  			ok = false
    98  			return min
    99  		}
   100  		return x
   101  	}
   102  
   103  	// Parse the date and time.
   104  	if len(s) < len("2006-01-02T15:04:05") {
   105  		return Time{}, false
   106  	}
   107  	year := parseUint(s[0:4], 0, 9999)                       // e.g., 2006
   108  	month := parseUint(s[5:7], 1, 12)                        // e.g., 01
   109  	day := parseUint(s[8:10], 1, daysIn(Month(month), year)) // e.g., 02
   110  	hour := parseUint(s[11:13], 0, 23)                       // e.g., 15
   111  	min := parseUint(s[14:16], 0, 59)                        // e.g., 04
   112  	sec := parseUint(s[17:19], 0, 59)                        // e.g., 05
   113  	if !ok || !(s[4] == '-' && s[7] == '-' && s[10] == 'T' && s[13] == ':' && s[16] == ':') {
   114  		return Time{}, false
   115  	}
   116  	s = s[19:]
   117  
   118  	// Parse the fractional second.
   119  	var nsec int
   120  	if len(s) >= 2 && s[0] == '.' && isDigit(s, 1) {
   121  		n := 2
   122  		for ; n < len(s) && isDigit(s, n); n++ {
   123  		}
   124  		nsec, _, _ = parseNanoseconds(s, n)
   125  		s = s[n:]
   126  	}
   127  
   128  	// Parse the time zone.
   129  	t := Date(year, Month(month), day, hour, min, sec, nsec, UTC)
   130  	if len(s) != 1 || s[0] != 'Z' {
   131  		if len(s) != len("-07:00") {
   132  			return Time{}, false
   133  		}
   134  		hr := parseUint(s[1:3], 0, 23) // e.g., 07
   135  		mm := parseUint(s[4:6], 0, 59) // e.g., 00
   136  		if !ok || !((s[0] == '-' || s[0] == '+') && s[3] == ':') {
   137  			return Time{}, false
   138  		}
   139  		zoneOffset := (hr*60 + mm) * 60
   140  		if s[0] == '-' {
   141  			zoneOffset *= -1
   142  		}
   143  		t.addSec(-int64(zoneOffset))
   144  
   145  		// Use local zone with the given offset if possible.
   146  		if _, offset, _, _, _ := local.lookup(t.unixSec()); offset == zoneOffset {
   147  			t.setLoc(local)
   148  		} else {
   149  			t.setLoc(FixedZone("", zoneOffset))
   150  		}
   151  	}
   152  	return t, true
   153  }
   154  
   155  func parseStrictRFC3339(b []byte) (Time, error) {
   156  	t, ok := parseRFC3339(b, Local)
   157  	if !ok {
   158  		t, err := Parse(RFC3339, string(b))
   159  		if err != nil {
   160  			return Time{}, err
   161  		}
   162  
   163  		// The parse template syntax cannot correctly validate RFC 3339.
   164  		// Explicitly check for cases that Parse is unable to validate for.
   165  		// See https://go.dev/issue/54580.
   166  		num2 := func(b []byte) byte { return 10*(b[0]-'0') + (b[1] - '0') }
   167  		switch {
   168  		// TODO(https://go.dev/issue/54580): Strict parsing is disabled for now.
   169  		// Enable this again with a GODEBUG opt-out.
   170  		case true:
   171  			return t, nil
   172  		case b[len("2006-01-02T")+1] == ':': // hour must be two digits
   173  			return Time{}, &ParseError{RFC3339, string(b), "15", string(b[len("2006-01-02T"):][:1]), ""}
   174  		case b[len("2006-01-02T15:04:05")] == ',': // sub-second separator must be a period
   175  			return Time{}, &ParseError{RFC3339, string(b), ".", ",", ""}
   176  		case b[len(b)-1] != 'Z':
   177  			switch {
   178  			case num2(b[len(b)-len("07:00"):]) >= 24: // timezone hour must be in range
   179  				return Time{}, &ParseError{RFC3339, string(b), "Z07:00", string(b[len(b)-len("Z07:00"):]), ": timezone hour out of range"}
   180  			case num2(b[len(b)-len("00"):]) >= 60: // timezone minute must be in range
   181  				return Time{}, &ParseError{RFC3339, string(b), "Z07:00", string(b[len(b)-len("Z07:00"):]), ": timezone minute out of range"}
   182  			}
   183  		default: // unknown error; should not occur
   184  			return Time{}, &ParseError{RFC3339, string(b), RFC3339, string(b), ""}
   185  		}
   186  	}
   187  	return t, nil
   188  }
   189  

View as plain text