github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/lang/funcs/datetime.go (about)

     1  package funcs
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	"github.com/zclconf/go-cty/cty"
     8  	"github.com/zclconf/go-cty/cty/function"
     9  )
    10  
    11  // TimestampFunc constructs a function that returns a string representation of the current date and time.
    12  var TimestampFunc = function.New(&function.Spec{
    13  	Params: []function.Parameter{},
    14  	Type:   function.StaticReturnType(cty.String),
    15  	Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
    16  		return cty.StringVal(time.Now().UTC().Format(time.RFC3339)), nil
    17  	},
    18  })
    19  
    20  // TimeAddFunc constructs a function that adds a duration to a timestamp, returning a new timestamp.
    21  var TimeAddFunc = function.New(&function.Spec{
    22  	Params: []function.Parameter{
    23  		{
    24  			Name: "timestamp",
    25  			Type: cty.String,
    26  		},
    27  		{
    28  			Name: "duration",
    29  			Type: cty.String,
    30  		},
    31  	},
    32  	Type: function.StaticReturnType(cty.String),
    33  	Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
    34  		ts, err := parseTimestamp(args[0].AsString())
    35  		if err != nil {
    36  			return cty.UnknownVal(cty.String), err
    37  		}
    38  		duration, err := time.ParseDuration(args[1].AsString())
    39  		if err != nil {
    40  			return cty.UnknownVal(cty.String), err
    41  		}
    42  
    43  		return cty.StringVal(ts.Add(duration).Format(time.RFC3339)), nil
    44  	},
    45  })
    46  
    47  // TimeCmpFunc is a function that compares two timestamps.
    48  var TimeCmpFunc = function.New(&function.Spec{
    49  	Params: []function.Parameter{
    50  		{
    51  			Name: "timestamp_a",
    52  			Type: cty.String,
    53  		},
    54  		{
    55  			Name: "timestamp_b",
    56  			Type: cty.String,
    57  		},
    58  	},
    59  	Type: function.StaticReturnType(cty.Number),
    60  	Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
    61  		tsA, err := parseTimestamp(args[0].AsString())
    62  		if err != nil {
    63  			return cty.UnknownVal(cty.String), function.NewArgError(0, err)
    64  		}
    65  		tsB, err := parseTimestamp(args[1].AsString())
    66  		if err != nil {
    67  			return cty.UnknownVal(cty.String), function.NewArgError(1, err)
    68  		}
    69  
    70  		switch {
    71  		case tsA.Equal(tsB):
    72  			return cty.NumberIntVal(0), nil
    73  		case tsA.Before(tsB):
    74  			return cty.NumberIntVal(-1), nil
    75  		default:
    76  			// By elimintation, tsA must be after tsB.
    77  			return cty.NumberIntVal(1), nil
    78  		}
    79  	},
    80  })
    81  
    82  // Timestamp returns a string representation of the current date and time.
    83  //
    84  // In the Terraform language, timestamps are conventionally represented as
    85  // strings using RFC 3339 "Date and Time format" syntax, and so timestamp
    86  // returns a string in this format.
    87  func Timestamp() (cty.Value, error) {
    88  	return TimestampFunc.Call([]cty.Value{})
    89  }
    90  
    91  // TimeAdd adds a duration to a timestamp, returning a new timestamp.
    92  //
    93  // In the Terraform language, timestamps are conventionally represented as
    94  // strings using RFC 3339 "Date and Time format" syntax. Timeadd requires
    95  // the timestamp argument to be a string conforming to this syntax.
    96  //
    97  // `duration` is a string representation of a time difference, consisting of
    98  // sequences of number and unit pairs, like `"1.5h"` or `1h30m`. The accepted
    99  // units are `ns`, `us` (or `µs`), `"ms"`, `"s"`, `"m"`, and `"h"`. The first
   100  // number may be negative to indicate a negative duration, like `"-2h5m"`.
   101  //
   102  // The result is a string, also in RFC 3339 format, representing the result
   103  // of adding the given direction to the given timestamp.
   104  func TimeAdd(timestamp cty.Value, duration cty.Value) (cty.Value, error) {
   105  	return TimeAddFunc.Call([]cty.Value{timestamp, duration})
   106  }
   107  
   108  // TimeCmp compares two timestamps, indicating whether they are equal or
   109  // if one is before the other.
   110  //
   111  // TimeCmp considers the UTC offset of each given timestamp when making its
   112  // decision, so for example 6:00 +0200 and 4:00 UTC are equal.
   113  //
   114  // In the Terraform language, timestamps are conventionally represented as
   115  // strings using RFC 3339 "Date and Time format" syntax. TimeCmp requires
   116  // the timestamp argument to be a string conforming to this syntax.
   117  //
   118  // The result is always a number between -1 and 1. -1 indicates that
   119  // timestampA is earlier than timestampB. 1 indicates that timestampA is
   120  // later. 0 indicates that the two timestamps represent the same instant.
   121  func TimeCmp(timestampA, timestampB cty.Value) (cty.Value, error) {
   122  	return TimeCmpFunc.Call([]cty.Value{timestampA, timestampB})
   123  }
   124  
   125  func parseTimestamp(ts string) (time.Time, error) {
   126  	t, err := time.Parse(time.RFC3339, ts)
   127  	if err != nil {
   128  		switch err := err.(type) {
   129  		case *time.ParseError:
   130  			// If err is a time.ParseError then its string representation is not
   131  			// appropriate since it relies on details of Go's strange date format
   132  			// representation, which a caller of our functions is not expected
   133  			// to be familiar with.
   134  			//
   135  			// Therefore we do some light transformation to get a more suitable
   136  			// error that should make more sense to our callers. These are
   137  			// still not awesome error messages, but at least they refer to
   138  			// the timestamp portions by name rather than by Go's example
   139  			// values.
   140  			if err.LayoutElem == "" && err.ValueElem == "" && err.Message != "" {
   141  				// For some reason err.Message is populated with a ": " prefix
   142  				// by the time package.
   143  				return time.Time{}, fmt.Errorf("not a valid RFC3339 timestamp%s", err.Message)
   144  			}
   145  			var what string
   146  			switch err.LayoutElem {
   147  			case "2006":
   148  				what = "year"
   149  			case "01":
   150  				what = "month"
   151  			case "02":
   152  				what = "day of month"
   153  			case "15":
   154  				what = "hour"
   155  			case "04":
   156  				what = "minute"
   157  			case "05":
   158  				what = "second"
   159  			case "Z07:00":
   160  				what = "UTC offset"
   161  			case "T":
   162  				return time.Time{}, fmt.Errorf("not a valid RFC3339 timestamp: missing required time introducer 'T'")
   163  			case ":", "-":
   164  				if err.ValueElem == "" {
   165  					return time.Time{}, fmt.Errorf("not a valid RFC3339 timestamp: end of string where %q is expected", err.LayoutElem)
   166  				} else {
   167  					return time.Time{}, fmt.Errorf("not a valid RFC3339 timestamp: found %q where %q is expected", err.ValueElem, err.LayoutElem)
   168  				}
   169  			default:
   170  				// Should never get here, because time.RFC3339 includes only the
   171  				// above portions, but since that might change in future we'll
   172  				// be robust here.
   173  				what = "timestamp segment"
   174  			}
   175  			if err.ValueElem == "" {
   176  				return time.Time{}, fmt.Errorf("not a valid RFC3339 timestamp: end of string before %s", what)
   177  			} else {
   178  				return time.Time{}, fmt.Errorf("not a valid RFC3339 timestamp: cannot use %q as %s", err.ValueElem, what)
   179  			}
   180  		}
   181  		return time.Time{}, err
   182  	}
   183  	return t, nil
   184  }