/pkg/expressions/stdlib/funcsTime.go

https://github.com/zix99/rare · Go · 196 lines · 162 code · 26 blank · 8 comment · 39 complexity · e100889b45c0ad7de045ef8ad10c0acc MD5 · raw file

  1. package stdlib
  2. import (
  3. "strconv"
  4. "strings"
  5. "sync/atomic"
  6. "time"
  7. . "rare/pkg/expressions" //lint:ignore ST1001 Legacy
  8. "github.com/araddon/dateparse"
  9. )
  10. const defaultTimeFormat = time.RFC3339
  11. var timeFormats = map[string]string{
  12. // Standard formats
  13. "ASNIC": time.ANSIC,
  14. "UNIX": time.UnixDate,
  15. "RUBY": time.RubyDate,
  16. "RFC822": time.RFC822,
  17. "RFC822Z": time.RFC822Z,
  18. "RFC1123": time.RFC1123,
  19. "RFC1123Z": time.RFC1123Z,
  20. "RFC3339": time.RFC3339,
  21. "RFC3339N": time.RFC3339Nano,
  22. // Custom formats,
  23. "NGINX": "_2/Jan/2006:15:04:05 -0700",
  24. // Parts,
  25. "MONTH": "01",
  26. "DAY": "_2",
  27. "YEAR": "2006",
  28. "HOUR": "15",
  29. "MINUTE": "04",
  30. "SECOND": "05",
  31. "TIMEZONE": "MST",
  32. "NTIMEZONE": "-0700",
  33. "NTZ": "-0700",
  34. }
  35. func namedTimeFormatToFormat(f string) string {
  36. if mapped, ok := timeFormats[strings.ToUpper(f)]; ok {
  37. return mapped
  38. }
  39. return f
  40. }
  41. func smartDateParseWrapper(format string, dateStage KeyBuilderStage, f func(time time.Time) string) KeyBuilderStage {
  42. switch strings.ToLower(format) {
  43. case "auto": // Auto will attempt to parse every time
  44. return KeyBuilderStage(func(context KeyBuilderContext) string {
  45. strTime := dateStage(context)
  46. val, err := dateparse.ParseAny(strTime)
  47. if err != nil {
  48. return ErrorParsing
  49. }
  50. return f(val)
  51. })
  52. case "": // Empty format will auto-detect on first successful entry
  53. var atomicFormat atomic.Value
  54. atomicFormat.Store("")
  55. return KeyBuilderStage(func(context KeyBuilderContext) string {
  56. strTime := dateStage(context)
  57. if strTime == "" { // This is important for future optimization efforts (so an empty string won't be remembered as a valid format)
  58. return ErrorParsing
  59. }
  60. liveFormat := atomicFormat.Load().(string)
  61. if liveFormat == "" {
  62. // This may end up run by a few different threads, but it comes at the benefit
  63. // of not needing a mutex
  64. var err error
  65. liveFormat, err = dateparse.ParseFormat(strTime)
  66. if err != nil {
  67. return ErrorParsing
  68. }
  69. atomicFormat.Store(liveFormat)
  70. }
  71. val, err := time.Parse(liveFormat, strTime)
  72. if err != nil {
  73. return ErrorParsing
  74. }
  75. return f(val)
  76. })
  77. default: // non-empty; Set format will resolve to a go date
  78. parseFormat := namedTimeFormatToFormat(format)
  79. return KeyBuilderStage(func(context KeyBuilderContext) string {
  80. strTime := dateStage(context)
  81. val, err := time.Parse(parseFormat, strTime)
  82. if err != nil {
  83. return ErrorParsing
  84. }
  85. return f(val)
  86. })
  87. }
  88. }
  89. // Parse time into standard unix epoch time (easier to use)
  90. func kfTimeParse(args []KeyBuilderStage) KeyBuilderStage {
  91. if len(args) < 1 {
  92. return stageError(ErrorArgCount)
  93. }
  94. // Special key-words for time (eg "now")
  95. if val, ok := EvalStaticStage(args[0]); ok {
  96. switch strings.ToLower(val) {
  97. case "now":
  98. now := strconv.FormatInt(time.Now().Unix(), 10)
  99. return func(context KeyBuilderContext) string {
  100. return now
  101. }
  102. }
  103. }
  104. // Specific format denoted
  105. format := EvalStageIndexOrDefault(args, 1, "")
  106. return smartDateParseWrapper(format, args[0], func(t time.Time) string {
  107. return strconv.FormatInt(t.Unix(), 10)
  108. })
  109. }
  110. func kfTimeFormat(args []KeyBuilderStage) KeyBuilderStage {
  111. if len(args) < 1 {
  112. return stageError(ErrorArgCount)
  113. }
  114. format := namedTimeFormatToFormat(EvalStageIndexOrDefault(args, 1, defaultTimeFormat))
  115. utc := Truthy(EvalStageIndexOrDefault(args, 2, ""))
  116. return KeyBuilderStage(func(context KeyBuilderContext) string {
  117. strUnixTime := args[0](context)
  118. unixTime, err := strconv.ParseInt(strUnixTime, 10, 64)
  119. if err != nil {
  120. return ErrorType
  121. }
  122. t := time.Unix(unixTime, 0)
  123. if utc {
  124. t = t.UTC()
  125. }
  126. return t.Format(format)
  127. })
  128. }
  129. func kfDuration(args []KeyBuilderStage) KeyBuilderStage {
  130. if len(args) != 1 {
  131. return stageError(ErrorArgCount)
  132. }
  133. return KeyBuilderStage(func(context KeyBuilderContext) string {
  134. strDuration := args[0](context)
  135. duration, err := time.ParseDuration(strDuration)
  136. if err != nil {
  137. return ErrorType
  138. }
  139. return strconv.FormatInt(int64(duration.Seconds()), 10)
  140. })
  141. }
  142. func kfBucketTime(args []KeyBuilderStage) KeyBuilderStage {
  143. if len(args) < 2 {
  144. return stageError(ErrorArgCount)
  145. }
  146. bucketFormat := timeBucketToFormat(EvalStageOrDefault(args[1], "day"))
  147. parseFormat := EvalStageIndexOrDefault(args, 2, "")
  148. return smartDateParseWrapper(parseFormat, args[0], func(t time.Time) string {
  149. return t.Format(bucketFormat)
  150. })
  151. }
  152. func timeBucketToFormat(name string) string {
  153. name = strings.ToLower(name)
  154. if isPartialString(name, "nanos") {
  155. return "2006-01-02 15:04:05.999999999"
  156. } else if isPartialString(name, "seconds") {
  157. return "2006-01-02 15:04:05"
  158. } else if isPartialString(name, "minutes") {
  159. return "2006-01-02 15:04"
  160. } else if isPartialString(name, "hours") {
  161. return "2006-01-02 15"
  162. } else if isPartialString(name, "days") {
  163. return "2006-01-02"
  164. } else if isPartialString(name, "months") {
  165. return "2006-01"
  166. } else if isPartialString(name, "years") {
  167. return "2006"
  168. }
  169. return ErrorBucket
  170. }