package bifs

import (
	"fmt"
	"regexp"
	"strings"
	"time"

	strptime "github.com/johnkerl/miller/v6/pkg/pbnjay-strptime"
	"github.com/lestrrat-go/strftime"

	"github.com/johnkerl/miller/v6/pkg/lib"
	"github.com/johnkerl/miller/v6/pkg/mlrval"
)

const ISO8601_TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"

var ptr_ISO8601_TIME_FORMAT = mlrval.FromString("%Y-%m-%dT%H:%M:%SZ")
var ptr_ISO8601_LOCAL_TIME_FORMAT = mlrval.FromString("%Y-%m-%d %H:%M:%S")
var ptr_YMD_FORMAT = mlrval.FromString("%Y-%m-%d")

func BIF_systime() *mlrval.Mlrval {
	return mlrval.FromFloat(
		float64(time.Now().UnixNano()) / 1.0e9,
	)
}
func BIF_systimeint() *mlrval.Mlrval {
	return mlrval.FromInt(time.Now().Unix())
}

func BIF_sysntime() *mlrval.Mlrval {
	return mlrval.FromInt(time.Now().UnixNano())
}

var startTime float64
var startNTime int64

func init() {
	startTime = float64(time.Now().UnixNano()) / 1.0e9
	startNTime = time.Now().UnixNano()
}
func BIF_uptime() *mlrval.Mlrval {
	return mlrval.FromFloat(
		float64(time.Now().UnixNano())/1.0e9 - startTime,
	)
}
func BIF_upntime() *mlrval.Mlrval {
	return mlrval.FromInt(
		time.Now().UnixNano() - startNTime,
	)
}

func BIF_sec2gmt_unary(input1 *mlrval.Mlrval) *mlrval.Mlrval {
	floatValue, isNumeric := input1.GetNumericToFloatValue()
	if !isNumeric {
		return input1
	}
	numDecimalPlaces := 0
	return mlrval.FromString(lib.Sec2GMT(floatValue, numDecimalPlaces))
}

func BIF_nsec2gmt_unary(input1 *mlrval.Mlrval) *mlrval.Mlrval {
	intValue, errValue := input1.GetIntValueOrError("nsec2gmt")
	if errValue != nil {
		return errValue
	}
	numDecimalPlaces := 0
	return mlrval.FromString(lib.Nsec2GMT(intValue, numDecimalPlaces))
}

func BIF_sec2gmt_binary(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	floatValue, errValue := input1.GetNumericToFloatValueOrError("sec2gmt")
	if errValue != nil {
		return errValue
	}
	numDecimalPlaces, errValue := input2.GetIntValueOrError("sec2gmt")
	if errValue != nil {
		return errValue
	}
	return mlrval.FromString(lib.Sec2GMT(floatValue, int(numDecimalPlaces)))
}

func BIF_nsec2gmt_binary(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	intValue, ok := input1.GetIntValue()
	if !ok {
		return input1
	}
	numDecimalPlaces, errValue := input2.GetIntValueOrError("nsec2gmt")
	if errValue != nil {
		return errValue
	}
	return mlrval.FromString(lib.Nsec2GMT(intValue, int(numDecimalPlaces)))
}

func BIF_sec2localtime_unary(input1 *mlrval.Mlrval) *mlrval.Mlrval {
	floatValue, isNumeric := input1.GetNumericToFloatValue()
	if !isNumeric {
		return input1
	}
	numDecimalPlaces := 0
	return mlrval.FromString(lib.Sec2LocalTime(floatValue, numDecimalPlaces))
}

func BIF_nsec2localtime_unary(input1 *mlrval.Mlrval) *mlrval.Mlrval {
	intValue, ok := input1.GetIntValue()
	if !ok {
		return input1
	}
	numDecimalPlaces := 0
	return mlrval.FromString(lib.Nsec2LocalTime(intValue, numDecimalPlaces))
}

func BIF_sec2localtime_binary(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	floatValue, isNumeric := input1.GetNumericToFloatValue()
	if !isNumeric {
		return input1
	}
	numDecimalPlaces, errValue := input2.GetIntValueOrError("sec2localtime")
	if errValue != nil {
		return errValue
	}
	return mlrval.FromString(lib.Sec2LocalTime(floatValue, int(numDecimalPlaces)))
}

func BIF_nsec2localtime_binary(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	intValue, ok := input1.GetIntValue()
	if !ok {
		return input1
	}
	numDecimalPlaces, errValue := input2.GetIntValueOrError("nsec2localtime")
	if errValue != nil {
		return errValue
	}
	return mlrval.FromString(lib.Nsec2LocalTime(intValue, int(numDecimalPlaces)))
}

func BIF_sec2localtime_ternary(input1, input2, input3 *mlrval.Mlrval) *mlrval.Mlrval {
	floatValue, isNumeric := input1.GetNumericToFloatValue()
	if !isNumeric {
		return input1
	}
	numDecimalPlaces, errValue := input2.GetIntValueOrError("sec2localtime")
	if errValue != nil {
		return errValue
	}
	locationString, errValue := input3.GetStringValueOrError("sec2localtime")
	if errValue != nil {
		return errValue
	}
	location, err := time.LoadLocation(locationString)
	if err != nil {
		return mlrval.FromError(err)
	}
	return mlrval.FromString(lib.Sec2LocationTime(floatValue, int(numDecimalPlaces), location))
}

func BIF_nsec2localtime_ternary(input1, input2, input3 *mlrval.Mlrval) *mlrval.Mlrval {
	intValue, isNumeric := input1.GetIntValue()
	if !isNumeric {
		return input1
	}
	numDecimalPlaces, errValue := input2.GetIntValueOrError("nsec2localtime")
	if errValue != nil {
		return errValue
	}
	locationString, errValue := input3.GetStringValueOrError("nsec2localtime")
	if errValue != nil {
		return errValue
	}
	location, err := time.LoadLocation(locationString)
	if err != nil {
		return mlrval.FromError(err)
	}
	return mlrval.FromString(lib.Nsec2LocationTime(intValue, int(numDecimalPlaces), location))
}

func BIF_sec2gmtdate(input1 *mlrval.Mlrval) *mlrval.Mlrval {
	if !input1.IsNumeric() {
		return input1
	}
	return BIF_strftime(input1, ptr_YMD_FORMAT)
}

func BIF_nsec2gmtdate(input1 *mlrval.Mlrval) *mlrval.Mlrval {
	if !input1.IsNumeric() {
		return input1
	}
	return BIF_strfntime(input1, ptr_YMD_FORMAT)
}

func BIF_sec2localdate_unary(input1 *mlrval.Mlrval) *mlrval.Mlrval {
	if !input1.IsNumeric() {
		return input1
	}
	return BIF_strftime_local_binary(input1, ptr_YMD_FORMAT)
}

func BIF_nsec2localdate_unary(input1 *mlrval.Mlrval) *mlrval.Mlrval {
	if !input1.IsNumeric() {
		return input1
	}
	return BIF_strfntime_local_binary(input1, ptr_YMD_FORMAT)
}

func BIF_sec2localdate_binary(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	if !input1.IsNumeric() {
		return input1
	}
	return BIF_strftime_local_ternary(input1, ptr_YMD_FORMAT, input2)
}

func BIF_nsec2localdate_binary(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	if !input1.IsNumeric() {
		return input1
	}
	return BIF_strfntime_local_ternary(input1, ptr_YMD_FORMAT, input2)
}

func BIF_localtime2gmt_unary(input1 *mlrval.Mlrval) *mlrval.Mlrval {
	if !input1.IsString() {
		return mlrval.FromNotStringError("localtime2gmt", input1)
	}
	return BIF_nsec2gmt_unary(BIF_localtime2nsec_unary(input1))
}

func BIF_localtime2gmt_binary(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	if !input1.IsString() {
		return mlrval.FromNotStringError("localtime2gmt", input1)
	}
	return BIF_nsec2gmt_unary(BIF_localtime2nsec_binary(input1, input2))
}

func BIF_gmt2localtime_unary(input1 *mlrval.Mlrval) *mlrval.Mlrval {
	if !input1.IsString() {
		return mlrval.FromNotStringError("gmt2localtime2", input1)
	}
	return BIF_nsec2localtime_unary(BIF_gmt2nsec(input1))
}

func BIF_gmt2localtime_binary(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	if !input1.IsString() {
		return mlrval.FromNotStringError("gmt2localtime2", input1)
	}
	return BIF_nsec2localtime_ternary(BIF_gmt2nsec(input1), mlrval.FromInt(0), input2)
}

// Argument 1 is int/float seconds since the epoch.
// Argument 2 is format string like "%Y-%m-%d %H:%M:%S".

var extensionRegex = regexp.MustCompile("([1-9])S")

func BIF_strftime(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	return strftimeHelper(input1, input2, false, nil, "strftime")
}

func BIF_strfntime(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	return strfntimeHelper(input1, input2, false, nil, "strfntime")
}

func BIF_strftime_local_binary(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	return strftimeHelper(input1, input2, true, nil, "strftime_local")
}

func BIF_strfntime_local_binary(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	return strfntimeHelper(input1, input2, true, nil, "strfntime_local")
}

func BIF_strftime_local_ternary(input1, input2, input3 *mlrval.Mlrval) *mlrval.Mlrval {
	locationString, errValue := input3.GetStringValueOrError("strftime")
	if errValue != nil {
		return errValue
	}

	location, err := time.LoadLocation(locationString)
	if err != nil {
		return mlrval.FromError(err)
	}

	return strftimeHelper(input1, input2, true, location, "strftime_local")
}

func BIF_strfntime_local_ternary(input1, input2, input3 *mlrval.Mlrval) *mlrval.Mlrval {
	locationString, errValue := input3.GetStringValueOrError("strfntime")
	if errValue != nil {
		return errValue
	}
	location, err := time.LoadLocation(locationString)
	if err != nil {
		return mlrval.FromError(err)
	}
	return strfntimeHelper(input1, input2, true, location, "strfntime_local")
}

func strftimeHelper(
	input1, input2 *mlrval.Mlrval,
	doLocal bool,
	location *time.Location,
	funcname string,
) *mlrval.Mlrval {
	if input1.IsVoid() {
		return input1
	}
	epochSeconds, errValue := input1.GetNumericToFloatValueOrError(funcname)
	if errValue != nil {
		return errValue
	}
	if !input2.IsString() {
		return mlrval.FromNotStringError(funcname, input2)
	}

	// Convert argument1 from float seconds since the epoch to a Go time.
	var inputTime time.Time
	if doLocal {
		if location != nil {
			inputTime = lib.EpochSecondsToLocationTime(epochSeconds, location)
		} else {
			inputTime = lib.EpochSecondsToLocalTime(epochSeconds)
		}
	} else {
		inputTime = lib.EpochSecondsToGMT(epochSeconds)
	}

	// Convert argument 2 to a strftime format string.
	//
	// Miller fractional-second formats are like "%6S", and were so in the C
	// implementation. However, in the strftime package we're using in the Go
	// port, extension-formats are only a single byte so we need to rewrite
	// them to "%6".
	formatString := extensionRegex.ReplaceAllString(input2.AcquireStringValue(), "$1")

	formatter, err := strftime.New(formatString, strftimeExtensions)
	if err != nil {
		return mlrval.FromError(err)
	}

	outputString := formatter.FormatString(inputTime)

	return mlrval.FromString(outputString)
}

func strfntimeHelper(
	input1, input2 *mlrval.Mlrval,
	doLocal bool,
	location *time.Location,
	funcname string,
) *mlrval.Mlrval {
	if input1.IsVoid() {
		return input1
	}
	epochNanoseconds, errValue := input1.GetIntValueOrError(funcname)
	if errValue != nil {
		return errValue
	}
	if !input2.IsString() {
		return mlrval.FromNotStringError(funcname, input2)
	}

	// Convert argument1 from float seconds since the epoch to a Go time.
	var inputTime time.Time
	if doLocal {
		if location != nil {
			inputTime = lib.EpochNanosecondsToLocationTime(epochNanoseconds, location)
		} else {
			inputTime = lib.EpochNanosecondsToLocalTime(epochNanoseconds)
		}
	} else {
		inputTime = lib.EpochNanosecondsToGMT(epochNanoseconds)
	}

	// Convert argument 2 to a strfntime format string.
	//
	// Miller fractional-second formats are like "%6S", and were so in the C
	// implementation. However, in the strfntime package we're using in the Go
	// port, extension-formats are only a single byte so we need to rewrite
	// them to "%6".
	formatString := extensionRegex.ReplaceAllString(input2.AcquireStringValue(), "$1")

	formatter, err := strftime.New(formatString, strftimeExtensions)
	if err != nil {
		return mlrval.FromError(err)
	}

	outputString := formatter.FormatString(inputTime)

	return mlrval.FromString(outputString)
}

// This is support for %1S .. %9S in format strings, using github.com/lestrrat-go/strftime.

var strftimeExtensions strftime.Option

// This is a helper function for the appenders below, which let people get
// 1..9 decimal places in the seconds of their strftime format strings.
func specificationHelper(b []byte, t time.Time, sprintfFormat string, quotient int) []byte {
	seconds := int(t.Second())
	fractional := int(t.Nanosecond() / quotient)
	secondsString := fmt.Sprintf("%02d", seconds)
	b = append(b, secondsString...)
	b = append(b, '.')
	fractionalString := fmt.Sprintf(sprintfFormat, fractional)
	b = append(b, fractionalString...)
	return b
}

func init() {
	appender1 := strftime.AppendFunc(func(b []byte, t time.Time) []byte {
		return specificationHelper(b, t, "%01d", 100000000)
	})
	appender2 := strftime.AppendFunc(func(b []byte, t time.Time) []byte {
		return specificationHelper(b, t, "%02d", 10000000)
	})
	appender3 := strftime.AppendFunc(func(b []byte, t time.Time) []byte {
		return specificationHelper(b, t, "%03d", 1000000)
	})
	appender4 := strftime.AppendFunc(func(b []byte, t time.Time) []byte {
		return specificationHelper(b, t, "%04d", 100000)
	})
	appender5 := strftime.AppendFunc(func(b []byte, t time.Time) []byte {
		return specificationHelper(b, t, "%05d", 10000)
	})
	appender6 := strftime.AppendFunc(func(b []byte, t time.Time) []byte {
		return specificationHelper(b, t, "%06d", 1000)
	})
	appender7 := strftime.AppendFunc(func(b []byte, t time.Time) []byte {
		return specificationHelper(b, t, "%07d", 100)
	})
	appender8 := strftime.AppendFunc(func(b []byte, t time.Time) []byte {
		return specificationHelper(b, t, "%09d", 10)
	})
	appender9 := strftime.AppendFunc(func(b []byte, t time.Time) []byte {
		return specificationHelper(b, t, "%09d", 1)
	})
	appenderN := strftime.AppendFunc(func(b []byte, t time.Time) []byte {
		nanos := int(t.Nanosecond())
		s := fmt.Sprintf("%09d", nanos)
		//return append(b, []byte(s))
		return append(b, s...)
	})
	appenderO := strftime.AppendFunc(func(b []byte, t time.Time) []byte {
		nanos := int(t.Nanosecond())
		s := fmt.Sprintf("%d", nanos)
		//return append(b, []byte(s))
		return append(b, s...)
	})
	appenderS := strftime.AppendFunc(func(b []byte, t time.Time) []byte {
		epochSeconds := t.Unix()
		s := fmt.Sprintf("%d", epochSeconds)
		return append(b, s...)
	})

	ss := strftime.NewSpecificationSet()
	_ = ss.Set('1', appender1)
	_ = ss.Set('2', appender2)
	_ = ss.Set('3', appender3)
	_ = ss.Set('4', appender4)
	_ = ss.Set('5', appender5)
	_ = ss.Set('6', appender6)
	_ = ss.Set('7', appender7)
	_ = ss.Set('8', appender8)
	_ = ss.Set('9', appender9)
	_ = ss.Set('N', appenderN)
	_ = ss.Set('O', appenderO)
	_ = ss.Set('s', appenderS)

	strftimeExtensions = strftime.WithSpecificationSet(ss)
}

// civilDaysBetween counts the days between two calendar dates, both taken at
// midnight UTC. Both Unix values are multiples of 86400 (Unix time has no leap
// seconds) so the division is exact even for negative differences.
func civilDaysBetween(y1 int, m1 time.Month, d1, y2 int, m2 time.Month, d2 int) int64 {
	t1 := time.Date(y1, m1, d1, 0, 0, 0, 0, time.UTC)
	t2 := time.Date(y2, m2, d2, 0, 0, 0, 0, time.UTC)
	return (t2.Unix() - t1.Unix()) / 86400
}

// BIF_datediff implements datediff(start, end, unit) in the manner of the
// DATEDIF function in spreadsheet programs. Arguments 1 and 2 are int/float
// seconds since the epoch; argument 3 is one of the unit strings "y", "m",
// "d", "ym", "yd", or "md" (case-insensitive). Differences are computed on
// calendar dates in GMT, ignoring any time-of-day parts. Unlike the
// spreadsheet versions, which error when start is after end, the result is
// negated in that case.
func BIF_datediff(input1, input2, input3 *mlrval.Mlrval) *mlrval.Mlrval {
	startSeconds, errValue := input1.GetNumericToFloatValueOrError("datediff")
	if errValue != nil {
		return errValue
	}
	endSeconds, errValue := input2.GetNumericToFloatValueOrError("datediff")
	if errValue != nil {
		return errValue
	}
	unit, errValue := input3.GetStringValueOrError("datediff")
	if errValue != nil {
		return errValue
	}

	startTime := lib.EpochSecondsToGMT(startSeconds)
	endTime := lib.EpochSecondsToGMT(endSeconds)

	sign := int64(1)
	if startTime.After(endTime) {
		startTime, endTime = endTime, startTime
		sign = -1
	}

	y1, m1, d1 := startTime.Date()
	y2, m2, d2 := endTime.Date()

	completeMonths := int64((y2-y1)*12 + int(m2) - int(m1))
	if d2 < d1 {
		completeMonths--
	}
	completeYears := int64(y2 - y1)
	if m2 < m1 || (m2 == m1 && d2 < d1) {
		completeYears--
	}

	var result int64
	switch strings.ToLower(unit) {
	case "d":
		result = civilDaysBetween(y1, m1, d1, y2, m2, d2)
	case "m":
		result = completeMonths
	case "y":
		result = completeYears
	case "ym":
		result = completeMonths - 12*completeYears
	case "md":
		if d2 >= d1 {
			result = int64(d2 - d1)
		} else {
			// Borrow from the month preceding the end month; time.Date
			// normalizes month zero to December of the prior year. As in the
			// spreadsheet implementations, this can go negative when the
			// start day-of-month exceeds the length of that preceding month.
			result = civilDaysBetween(y2, m2-1, d1, y2, m2, d2)
		}
	case "yd":
		// Advance the start date's year to put it within one year at or
		// before the end date.
		anchorYear := y2
		if m1 > m2 || (m1 == m2 && d1 > d2) {
			anchorYear--
		}
		result = civilDaysBetween(anchorYear, m1, d1, y2, m2, d2)
	default:
		return mlrval.FromErrorString(
			`datediff: unit argument must be one of "y", "m", "d", "ym", "yd", "md"; got "` + unit + `"`,
		)
	}

	return mlrval.FromInt(sign * result)
}

// Argument 1 is formatted date string like "2021-03-04 02:59:50".
// Argument 2 is format string like "%Y-%m-%d %H:%M:%S".
func BIF_strptime(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	return bif_strptime_unary_aux(input1, input2, false, false)
}

func BIF_strpntime(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	return bif_strptime_unary_aux(input1, input2, false, true)
}

func bif_strptime_unary_aux(input1, input2 *mlrval.Mlrval, doLocal, produceNanoseconds bool) *mlrval.Mlrval {
	if !input1.IsString() {
		return mlrval.FromNotStringError("strptime", input1)
	}
	if !input2.IsString() {
		return mlrval.FromNotStringError("strptime", input2)
	}
	timeString := input1.AcquireStringValue()
	formatString := input2.AcquireStringValue()

	var t time.Time
	var err error
	if doLocal {
		t, err = strptime.ParseLocal(timeString, formatString)
	} else {
		t, err = strptime.Parse(timeString, formatString)
	}
	if err != nil {
		return mlrval.FromError(err)
	}

	if produceNanoseconds {
		return mlrval.FromInt(t.UnixNano())
	}
	return mlrval.FromFloat(float64(t.UnixNano()) / 1.0e9)
}

// Argument 1 is formatted date string like "2021-03-04T02:59:50Z".
func BIF_gmt2sec(input1 *mlrval.Mlrval) *mlrval.Mlrval {
	return bif_strptime_unary_aux(input1, ptr_ISO8601_TIME_FORMAT, false, false)
}

// Argument 1 is formatted date string like "2021-03-04T02:59:50Z".
func BIF_gmt2nsec(input1 *mlrval.Mlrval) *mlrval.Mlrval {
	return bif_strptime_unary_aux(input1, ptr_ISO8601_TIME_FORMAT, false, true)
}

func BIF_localtime2sec_unary(input1 *mlrval.Mlrval) *mlrval.Mlrval {
	return bif_strptime_unary_aux(input1, ptr_ISO8601_LOCAL_TIME_FORMAT, true, false)
}

func BIF_localtime2nsec_unary(input1 *mlrval.Mlrval) *mlrval.Mlrval {
	return bif_strptime_unary_aux(input1, ptr_ISO8601_LOCAL_TIME_FORMAT, true, true)
}

func BIF_strptime_local_binary(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	return bif_strptime_binary_aux(input1, input2, true, false)
}

func BIF_strpntime_local_binary(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	return bif_strptime_binary_aux(input1, input2, true, true)
}

func bif_strptime_binary_aux(input1, input2 *mlrval.Mlrval, doLocal, produceNanoseconds bool) *mlrval.Mlrval {
	if !input1.IsString() {
		return mlrval.FromNotStringError("strptime", input1)
	}
	if !input2.IsString() {
		return mlrval.FromNotStringError("strptime", input2)
	}
	timeString := input1.AcquireStringValue()
	formatString := input2.AcquireStringValue()

	var t time.Time
	var err error
	if doLocal {
		t, err = strptime.ParseLocal(timeString, formatString)
	} else {
		t, err = strptime.Parse(timeString, formatString)
	}
	if err != nil {
		return mlrval.FromError(err)
	}

	if produceNanoseconds {
		return mlrval.FromInt(t.UnixNano())
	}
	return mlrval.FromFloat(float64(t.UnixNano()) / 1.0e9)
}

func BIF_strptime_local_ternary(input1, input2, input3 *mlrval.Mlrval) *mlrval.Mlrval {
	return bif_strptime_local_ternary_aux(input1, input2, input3, false)
}

func BIF_strpntime_local_ternary(input1, input2, input3 *mlrval.Mlrval) *mlrval.Mlrval {
	return bif_strptime_local_ternary_aux(input1, input2, input3, true)
}

func BIF_localtime2sec_binary(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	return bif_strptime_local_ternary_aux(input1, ptr_ISO8601_LOCAL_TIME_FORMAT, input2, false)
}

func BIF_localtime2nsec_binary(input1, input2 *mlrval.Mlrval) *mlrval.Mlrval {
	return bif_strptime_local_ternary_aux(input1, ptr_ISO8601_LOCAL_TIME_FORMAT, input2, true)
}

func bif_strptime_local_ternary_aux(input1, input2, input3 *mlrval.Mlrval, produceNanoseconds bool) *mlrval.Mlrval {
	if !input1.IsString() {
		return mlrval.FromNotStringError("strptime_local", input1)
	}
	if !input2.IsString() {
		return mlrval.FromNotStringError("strptime_local", input2)
	}
	if !input3.IsString() {
		return mlrval.FromNotStringError("strptime_local", input3)
	}

	timeString := input1.AcquireStringValue()
	formatString := input2.AcquireStringValue()
	locationString := input3.AcquireStringValue()

	location, err := time.LoadLocation(locationString)
	if err != nil {
		return mlrval.FromError(err)
	}

	t, err := strptime.ParseLocation(timeString, formatString, location)
	if err != nil {
		return mlrval.FromError(err)
	}

	if produceNanoseconds {
		return mlrval.FromInt(t.UnixNano())
	}
	return mlrval.FromFloat(float64(t.UnixNano()) / 1.0e9)
}
