github.com/mtsmfm/go/src@v0.0.0-20221020090648-44bdcb9f8fde/time/format_rfc3339.go (about)

     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  // RFC 3339 is the most commonly used format.
     8  //
     9  // It is implicitly used by the Time.(Marshal|Unmarshal)(Text|JSON) methods.
    10  // Also, according to analysis on https://go.dev/issue/52746,
    11  // RFC 3339 accounts for 57% of all explicitly specified time formats,
    12  // with the second most popular format only being used 8% of the time.
    13  // The overwhelming use of RFC 3339 compared to all other formats justifies
    14  // the addition of logic to optimize formatting and parsing.
    15  
    16  func (t Time) appendFormatRFC3339(b []byte, nanos bool) []byte {
    17  	_, offset, abs := t.locabs()
    18  
    19  	// Format date.
    20  	year, month, day, _ := absDate(abs, true)
    21  	b = appendInt(b, year, 4)
    22  	b = append(b, '-')
    23  	b = appendInt(b, int(month), 2)
    24  	b = append(b, '-')
    25  	b = appendInt(b, day, 2)
    26  
    27  	b = append(b, 'T')
    28  
    29  	// Format time.
    30  	hour, min, sec := absClock(abs)
    31  	b = appendInt(b, hour, 2)
    32  	b = append(b, ':')
    33  	b = appendInt(b, min, 2)
    34  	b = append(b, ':')
    35  	b = appendInt(b, sec, 2)
    36  
    37  	if nanos {
    38  		std := stdFracSecond(stdFracSecond9, 9, '.')
    39  		b = formatNano(b, uint(t.Nanosecond()), std)
    40  	}
    41  
    42  	if offset == 0 {
    43  		return append(b, 'Z')
    44  	}
    45  
    46  	// Format zone.
    47  	zone := offset / 60 // convert to minutes
    48  	if zone < 0 {
    49  		b = append(b, '-')
    50  		zone = -zone
    51  	} else {
    52  		b = append(b, '+')
    53  	}
    54  	b = appendInt(b, zone/60, 2)
    55  	b = append(b, ':')
    56  	b = appendInt(b, zone%60, 2)
    57  	return b
    58  }
    59  
    60  func parseRFC3339(s string, local *Location) (Time, bool) {
    61  	// parseUint parses s as an unsigned decimal integer and
    62  	// verifies that it is within some range.
    63  	// If it is invalid or out-of-range,
    64  	// it sets ok to false and returns the min value.
    65  	ok := true
    66  	parseUint := func(s string, min, max int) (x int) {
    67  		for _, c := range []byte(s) {
    68  			if c < '0' || '9' < c {
    69  				ok = false
    70  				return min
    71  			}
    72  			x = x*10 + int(c) - '0'
    73  		}
    74  		if x < min || max < x {
    75  			ok = false
    76  			return min
    77  		}
    78  		return x
    79  	}
    80  
    81  	// Parse the date and time.
    82  	if len(s) < len("2006-01-02T15:04:05") {
    83  		return Time{}, false
    84  	}
    85  	year := parseUint(s[0:4], 0, 9999)                       // e.g., 2006
    86  	month := parseUint(s[5:7], 1, 12)                        // e.g., 01
    87  	day := parseUint(s[8:10], 1, daysIn(Month(month), year)) // e.g., 02
    88  	hour := parseUint(s[11:13], 0, 23)                       // e.g., 15
    89  	min := parseUint(s[14:16], 0, 59)                        // e.g., 04
    90  	sec := parseUint(s[17:19], 0, 59)                        // e.g., 05
    91  	if !ok || !(s[4] == '-' && s[7] == '-' && s[10] == 'T' && s[13] == ':' && s[16] == ':') {
    92  		return Time{}, false
    93  	}
    94  	s = s[19:]
    95  
    96  	// Parse the fractional second.
    97  	var nsec int
    98  	if len(s) >= 2 && s[0] == '.' && isDigit(s, 1) {
    99  		n := 2
   100  		for ; n < len(s) && isDigit(s, n); n++ {
   101  		}
   102  		nsec, _, _ = parseNanoseconds(s, n)
   103  		s = s[n:]
   104  	}
   105  
   106  	// Parse the time zone.
   107  	t := Date(year, Month(month), day, hour, min, sec, nsec, UTC)
   108  	if s != "Z" {
   109  		if len(s) != len("-07:00") {
   110  			return Time{}, false
   111  		}
   112  		hr := parseUint(s[1:3], 0, 23) // e.g., 07
   113  		mm := parseUint(s[4:6], 0, 59) // e.g., 00
   114  		if !ok || !((s[0] == '-' || s[0] == '+') && s[3] == ':') {
   115  			return Time{}, false
   116  		}
   117  		zoneOffset := (hr*60 + mm) * 60
   118  		if s[0] == '-' {
   119  			zoneOffset *= -1
   120  		}
   121  		t.addSec(-int64(zoneOffset))
   122  
   123  		// Use local zone with the given offset if possible.
   124  		if _, offset, _, _, _ := local.lookup(t.unixSec()); offset == zoneOffset {
   125  			t.setLoc(local)
   126  		} else {
   127  			t.setLoc(FixedZone("", zoneOffset))
   128  		}
   129  	}
   130  	return t, true
   131  }