// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package time

import 

// RFC 3339 is the most commonly used format.
//
// It is implicitly used by the Time.(Marshal|Unmarshal)(Text|JSON) methods.
// Also, according to analysis on https://go.dev/issue/52746,
// RFC 3339 accounts for 57% of all explicitly specified time formats,
// with the second most popular format only being used 8% of the time.
// The overwhelming use of RFC 3339 compared to all other formats justifies
// the addition of logic to optimize formatting and parsing.

func ( Time) ( []byte,  bool) []byte {
	, ,  := .locabs()

	// Format date.
	, ,  := .days().date()
	 = appendInt(, , 4)
	 = append(, '-')
	 = appendInt(, int(), 2)
	 = append(, '-')
	 = appendInt(, , 2)

	 = append(, 'T')

	// Format time.
	, ,  := .clock()
	 = appendInt(, , 2)
	 = append(, ':')
	 = appendInt(, , 2)
	 = append(, ':')
	 = appendInt(, , 2)

	if  {
		 := stdFracSecond(stdFracSecond9, 9, '.')
		 = appendNano(, .Nanosecond(), )
	}

	if  == 0 {
		return append(, 'Z')
	}

	// Format zone.
	 :=  / 60 // convert to minutes
	if  < 0 {
		 = append(, '-')
		 = -
	} else {
		 = append(, '+')
	}
	 = appendInt(, /60, 2)
	 = append(, ':')
	 = appendInt(, %60, 2)
	return 
}

func ( Time) ( []byte) ([]byte, error) {
	 := len()
	 = .appendFormatRFC3339(, true)

	// Not all valid Go timestamps can be serialized as valid RFC 3339.
	// Explicitly check for these edge cases.
	// See https://go.dev/issue/4556 and https://go.dev/issue/54580.
	 := func( []byte) byte { return 10*([0]-'0') + ([1] - '0') }
	switch {
	case [+len("9999")] != '-': // year must be exactly 4 digits wide
		return , errors.New("year outside of range [0,9999]")
	case [len()-1] != 'Z':
		 := [len()-len("Z07:00")]
		if ('0' <=  &&  <= '9') || ([len()-len("07:00"):]) >= 24 {
			return , errors.New("timezone hour outside of range [0,23]")
		}
	}
	return , nil
}

func parseRFC3339[ []byte | string]( ,  *Location) (Time, bool) {
	// parseUint parses s as an unsigned decimal integer and
	// verifies that it is within some range.
	// If it is invalid or out-of-range,
	// it sets ok to false and returns the min value.
	 := true
	 := func( , ,  int) ( int) {
		for ,  := range []byte() {
			if  < '0' || '9' <  {
				 = false
				return 
			}
			 = *10 + int() - '0'
		}
		if  <  ||  <  {
			 = false
			return 
		}
		return 
	}

	// Parse the date and time.
	if len() < len("2006-01-02T15:04:05") {
		return Time{}, false
	}
	 := ([0:4], 0, 9999)                       // e.g., 2006
	 := ([5:7], 1, 12)                        // e.g., 01
	 := ([8:10], 1, daysIn(Month(), )) // e.g., 02
	 := ([11:13], 0, 23)                       // e.g., 15
	 := ([14:16], 0, 59)                        // e.g., 04
	 := ([17:19], 0, 59)                        // e.g., 05
	if ! || !([4] == '-' && [7] == '-' && [10] == 'T' && [13] == ':' && [16] == ':') {
		return Time{}, false
	}
	 = [19:]

	// Parse the fractional second.
	var  int
	if len() >= 2 && [0] == '.' && isDigit(, 1) {
		 := 2
		for ;  < len() && isDigit(, ); ++ {
		}
		, _, _ = parseNanoseconds(, )
		 = [:]
	}

	// Parse the time zone.
	 := Date(, Month(), , , , , , UTC)
	if len() != 1 || [0] != 'Z' {
		if len() != len("-07:00") {
			return Time{}, false
		}
		 := ([1:3], 0, 23) // e.g., 07
		 := ([4:6], 0, 59) // e.g., 00
		if ! || !(([0] == '-' || [0] == '+') && [3] == ':') {
			return Time{}, false
		}
		 := (*60 + ) * 60
		if [0] == '-' {
			 *= -1
		}
		.addSec(-int64())

		// Use local zone with the given offset if possible.
		if , , , ,  := .lookup(.unixSec());  ==  {
			.setLoc()
		} else {
			.setLoc(FixedZone("", ))
		}
	}
	return , true
}

func parseStrictRFC3339( []byte) (Time, error) {
	,  := parseRFC3339(, Local)
	if ! {
		,  := Parse(RFC3339, string())
		if  != nil {
			return Time{}, 
		}

		// The parse template syntax cannot correctly validate RFC 3339.
		// Explicitly check for cases that Parse is unable to validate for.
		// See https://go.dev/issue/54580.
		 := func( []byte) byte { return 10*([0]-'0') + ([1] - '0') }
		switch {
		// TODO(https://go.dev/issue/54580): Strict parsing is disabled for now.
		// Enable this again with a GODEBUG opt-out.
		case true:
			return , nil
		case [len("2006-01-02T")+1] == ':': // hour must be two digits
			return Time{}, &ParseError{RFC3339, string(), "15", string([len("2006-01-02T"):][:1]), ""}
		case [len("2006-01-02T15:04:05")] == ',': // sub-second separator must be a period
			return Time{}, &ParseError{RFC3339, string(), ".", ",", ""}
		case [len()-1] != 'Z':
			switch {
			case ([len()-len("07:00"):]) >= 24: // timezone hour must be in range
				return Time{}, &ParseError{RFC3339, string(), "Z07:00", string([len()-len("Z07:00"):]), ": timezone hour out of range"}
			case ([len()-len("00"):]) >= 60: // timezone minute must be in range
				return Time{}, &ParseError{RFC3339, string(), "Z07:00", string([len()-len("Z07:00"):]), ": timezone minute out of range"}
			}
		default: // unknown error; should not occur
			return Time{}, &ParseError{RFC3339, string(), RFC3339, string(), ""}
		}
	}
	return , nil
}