package bifs import ( "fmt" "regexp" "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) } // ================================================================ // 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()) } else { 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()) } else { 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()) } else { return mlrval.FromFloat(float64(t.UnixNano()) / 1.0e9) } }