window_test.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626
  1. package kubecost
  2. import (
  3. "fmt"
  4. "testing"
  5. "time"
  6. )
  7. func TestRoundBack(t *testing.T) {
  8. boulder := time.FixedZone("Boulder", -7*60*60)
  9. beijing := time.FixedZone("Beijing", 8*60*60)
  10. to := time.Date(2020, time.January, 1, 0, 0, 0, 0, boulder)
  11. tb := RoundBack(to, 24*time.Hour)
  12. if !tb.Equal(time.Date(2020, time.January, 1, 0, 0, 0, 0, boulder)) {
  13. t.Fatalf("RoundBack: expected 2020-01-01T00:00:00-07:00; actual %s", tb)
  14. }
  15. to = time.Date(2020, time.January, 1, 0, 0, 1, 0, boulder)
  16. tb = RoundBack(to, 24*time.Hour)
  17. if !tb.Equal(time.Date(2020, time.January, 1, 0, 0, 0, 0, boulder)) {
  18. t.Fatalf("RoundBack: expected 2020-01-01T00:00:00-07:00; actual %s", tb)
  19. }
  20. to = time.Date(2020, time.January, 1, 12, 37, 48, 0, boulder)
  21. tb = RoundBack(to, 24*time.Hour)
  22. if !tb.Equal(time.Date(2020, time.January, 1, 0, 0, 0, 0, boulder)) {
  23. t.Fatalf("RoundBack: expected 2020-01-01T00:00:00-07:00; actual %s", tb)
  24. }
  25. to = time.Date(2020, time.January, 1, 23, 37, 48, 0, boulder)
  26. tb = RoundBack(to, 24*time.Hour)
  27. if !tb.Equal(time.Date(2020, time.January, 1, 0, 0, 0, 0, boulder)) {
  28. t.Fatalf("RoundBack: expected 2020-01-01T00:00:00-07:00; actual %s", tb)
  29. }
  30. to = time.Date(2020, time.January, 1, 0, 0, 0, 0, beijing)
  31. tb = RoundBack(to, 24*time.Hour)
  32. if !tb.Equal(time.Date(2020, time.January, 1, 0, 0, 0, 0, beijing)) {
  33. t.Fatalf("RoundBack: expected 2020-01-01T00:00:00+08:00; actual %s", tb)
  34. }
  35. to = time.Date(2020, time.January, 1, 0, 0, 1, 0, beijing)
  36. tb = RoundBack(to, 24*time.Hour)
  37. if !tb.Equal(time.Date(2020, time.January, 1, 0, 0, 0, 0, beijing)) {
  38. t.Fatalf("RoundBack: expected 2020-01-01T00:00:00+08:00; actual %s", tb)
  39. }
  40. to = time.Date(2020, time.January, 1, 12, 37, 48, 0, beijing)
  41. tb = RoundBack(to, 24*time.Hour)
  42. if !tb.Equal(time.Date(2020, time.January, 1, 0, 0, 0, 0, beijing)) {
  43. t.Fatalf("RoundBack: expected 2020-01-01T00:00:00+08:00; actual %s", tb)
  44. }
  45. to = time.Date(2020, time.January, 1, 23, 59, 59, 0, beijing)
  46. tb = RoundBack(to, 24*time.Hour)
  47. if !tb.Equal(time.Date(2020, time.January, 1, 0, 0, 0, 0, beijing)) {
  48. t.Fatalf("RoundBack: expected 2020-01-01T00:00:00+08:00; actual %s", tb)
  49. }
  50. to = time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)
  51. tb = RoundBack(to, 24*time.Hour)
  52. if !tb.Equal(time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) {
  53. t.Fatalf("RoundBack: expected 2020-01-01T00:00:00Z; actual %s", tb)
  54. }
  55. to = time.Date(2020, time.January, 1, 0, 0, 1, 0, time.UTC)
  56. tb = RoundBack(to, 24*time.Hour)
  57. if !tb.Equal(time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) {
  58. t.Fatalf("RoundBack: expected 2020-01-01T00:00:00Z; actual %s", tb)
  59. }
  60. to = time.Date(2020, time.January, 1, 12, 37, 48, 0, time.UTC)
  61. tb = RoundBack(to, 24*time.Hour)
  62. if !tb.Equal(time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) {
  63. t.Fatalf("RoundBack: expected 2020-01-01T00:00:00Z; actual %s", tb)
  64. }
  65. to = time.Date(2020, time.January, 1, 23, 59, 0, 0, time.UTC)
  66. tb = RoundBack(to, 24*time.Hour)
  67. if !tb.Equal(time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) {
  68. t.Fatalf("RoundBack: expected 2020-01-01T00:00:00Z; actual %s", tb)
  69. }
  70. }
  71. func TestRoundForward(t *testing.T) {
  72. boulder := time.FixedZone("Boulder", -7*60*60)
  73. beijing := time.FixedZone("Beijing", 8*60*60)
  74. to := time.Date(2020, time.January, 1, 0, 0, 0, 0, boulder)
  75. tb := RoundForward(to, 24*time.Hour)
  76. if !tb.Equal(time.Date(2020, time.January, 1, 0, 0, 0, 0, boulder)) {
  77. t.Fatalf("RoundForward: expected 2020-01-01T00:00:00-07:00; actual %s", tb)
  78. }
  79. to = time.Date(2020, time.January, 1, 0, 0, 1, 0, boulder)
  80. tb = RoundForward(to, 24*time.Hour)
  81. if !tb.Equal(time.Date(2020, time.January, 2, 0, 0, 0, 0, boulder)) {
  82. t.Fatalf("RoundForward: expected 2020-01-02T00:00:00-07:00; actual %s", tb)
  83. }
  84. to = time.Date(2020, time.January, 1, 12, 37, 48, 0, boulder)
  85. tb = RoundForward(to, 24*time.Hour)
  86. if !tb.Equal(time.Date(2020, time.January, 2, 0, 0, 0, 0, boulder)) {
  87. t.Fatalf("RoundForward: expected 2020-01-02T00:00:00-07:00; actual %s", tb)
  88. }
  89. to = time.Date(2020, time.January, 1, 23, 37, 48, 0, boulder)
  90. tb = RoundForward(to, 24*time.Hour)
  91. if !tb.Equal(time.Date(2020, time.January, 2, 0, 0, 0, 0, boulder)) {
  92. t.Fatalf("RoundForward: expected 2020-01-02T00:00:00-07:00; actual %s", tb)
  93. }
  94. to = time.Date(2020, time.January, 1, 0, 0, 0, 0, beijing)
  95. tb = RoundForward(to, 24*time.Hour)
  96. if !tb.Equal(time.Date(2020, time.January, 1, 0, 0, 0, 0, beijing)) {
  97. t.Fatalf("RoundForward: expected 2020-01-01T00:00:00+08:00; actual %s", tb)
  98. }
  99. to = time.Date(2020, time.January, 1, 0, 0, 1, 0, beijing)
  100. tb = RoundForward(to, 24*time.Hour)
  101. if !tb.Equal(time.Date(2020, time.January, 2, 0, 0, 0, 0, beijing)) {
  102. t.Fatalf("RoundForward: expected 2020-01-02T00:00:00+08:00; actual %s", tb)
  103. }
  104. to = time.Date(2020, time.January, 1, 12, 37, 48, 0, beijing)
  105. tb = RoundForward(to, 24*time.Hour)
  106. if !tb.Equal(time.Date(2020, time.January, 2, 0, 0, 0, 0, beijing)) {
  107. t.Fatalf("RoundForward: expected 2020-01-02T00:00:00+08:00; actual %s", tb)
  108. }
  109. to = time.Date(2020, time.January, 1, 23, 59, 59, 0, beijing)
  110. tb = RoundForward(to, 24*time.Hour)
  111. if !tb.Equal(time.Date(2020, time.January, 2, 0, 0, 0, 0, beijing)) {
  112. t.Fatalf("RoundForward: expected 2020-01-02T00:00:00+08:00; actual %s", tb)
  113. }
  114. to = time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)
  115. tb = RoundForward(to, 24*time.Hour)
  116. if !tb.Equal(time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) {
  117. t.Fatalf("RoundForward: expected 2020-01-01T00:00:00Z; actual %s", tb)
  118. }
  119. to = time.Date(2020, time.January, 1, 0, 0, 1, 0, time.UTC)
  120. tb = RoundForward(to, 24*time.Hour)
  121. if !tb.Equal(time.Date(2020, time.January, 2, 0, 0, 0, 0, time.UTC)) {
  122. t.Fatalf("RoundForward: expected 2020-01-02T00:00:00Z; actual %s", tb)
  123. }
  124. to = time.Date(2020, time.January, 1, 12, 37, 48, 0, time.UTC)
  125. tb = RoundForward(to, 24*time.Hour)
  126. if !tb.Equal(time.Date(2020, time.January, 2, 0, 0, 0, 0, time.UTC)) {
  127. t.Fatalf("RoundForward: expected 2020-01-02T00:00:00Z; actual %s", tb)
  128. }
  129. to = time.Date(2020, time.January, 1, 23, 59, 0, 0, time.UTC)
  130. tb = RoundForward(to, 24*time.Hour)
  131. if !tb.Equal(time.Date(2020, time.January, 2, 0, 0, 0, 0, time.UTC)) {
  132. t.Fatalf("RoundForward: expected 2020-01-02T00:00:00Z; actual %s", tb)
  133. }
  134. }
  135. func TestParseWindowUTC(t *testing.T) {
  136. now := time.Now().UTC()
  137. // "today" should span Now() and not produce an error
  138. today, err := ParseWindowUTC("today")
  139. if err != nil {
  140. t.Fatalf(`unexpected error parsing "today": %s`, err)
  141. }
  142. if today.Duration().Hours() != 24 {
  143. t.Fatalf(`expect: window "today" to have duration 24 hour; actual: %f hours`, today.Duration().Hours())
  144. }
  145. if !today.Contains(time.Now().UTC()) {
  146. t.Fatalf(`expect: window "today" to contain now; actual: %s`, today)
  147. }
  148. // "yesterday" should span Now() and not produce an error
  149. yesterday, err := ParseWindowUTC("yesterday")
  150. if err != nil {
  151. t.Fatalf(`unexpected error parsing "yesterday": %s`, err)
  152. }
  153. if yesterday.Duration().Hours() != 24 {
  154. t.Fatalf(`expect: window "yesterday" to have duration 24 hour; actual: %f hours`, yesterday.Duration().Hours())
  155. }
  156. if !yesterday.End().Before(time.Now().UTC()) {
  157. t.Fatalf(`expect: window "yesterday" to end before now; actual: %s ends after %s`, yesterday, time.Now().UTC())
  158. }
  159. week, err := ParseWindowUTC("week")
  160. hoursThisWeek := float64(time.Now().UTC().Weekday()) * 24.0
  161. if err != nil {
  162. t.Fatalf(`unexpected error parsing "week": %s`, err)
  163. }
  164. if week.Duration().Hours() < hoursThisWeek {
  165. t.Fatalf(`expect: window "week" to have at least %f hours; actual: %f hours`, hoursThisWeek, week.Duration().Hours())
  166. }
  167. if !week.End().Before(time.Now().UTC()) {
  168. t.Fatalf(`expect: window "week" to end before now; actual: %s ends after %s`, week, time.Now().UTC())
  169. }
  170. month, err := ParseWindowUTC("month")
  171. hoursThisMonth := float64(time.Now().UTC().Day()) * 24.0
  172. if err != nil {
  173. t.Fatalf(`unexpected error parsing "month": %s`, err)
  174. }
  175. if month.Duration().Hours() > hoursThisMonth || month.Duration().Hours() < (hoursThisMonth-24.0) {
  176. t.Fatalf(`expect: window "month" to have approximately %f hours; actual: %f hours`, hoursThisMonth, month.Duration().Hours())
  177. }
  178. if !month.End().Before(time.Now().UTC()) {
  179. t.Fatalf(`expect: window "month" to end before now; actual: %s ends after %s`, month, time.Now().UTC())
  180. }
  181. // TODO niko/etl lastweek
  182. lastmonth, err := ParseWindowUTC("lastmonth")
  183. monthMinHours := float64(24 * 28)
  184. monthMaxHours := float64(24 * 31)
  185. firstOfMonth := now.Truncate(time.Hour * 24).Add(-24 * time.Hour * time.Duration(now.Day()-1))
  186. if err != nil {
  187. t.Fatalf(`unexpected error parsing "lastmonth": %s`, err)
  188. }
  189. if lastmonth.Duration().Hours() > monthMaxHours || lastmonth.Duration().Hours() < monthMinHours {
  190. t.Fatalf(`expect: window "lastmonth" to have approximately %f hours; actual: %f hours`, hoursThisMonth, lastmonth.Duration().Hours())
  191. }
  192. if !lastmonth.End().Equal(firstOfMonth) {
  193. t.Fatalf(`expect: window "lastmonth" to end on the first of the current month; actual: %s doesn't end on %s`, lastmonth, firstOfMonth)
  194. }
  195. ago12h := time.Now().UTC().Add(-12 * time.Hour)
  196. ago36h := time.Now().UTC().Add(-36 * time.Hour)
  197. ago60h := time.Now().UTC().Add(-60 * time.Hour)
  198. // "24h" should have 24 hour duration and not produce an error
  199. dur24h, err := ParseWindowUTC("24h")
  200. if err != nil {
  201. t.Fatalf(`unexpected error parsing "24h": %s`, err)
  202. }
  203. if dur24h.Duration().Hours() != 24 {
  204. t.Fatalf(`expect: window "24h" to have duration 24 hour; actual: %f hours`, dur24h.Duration().Hours())
  205. }
  206. if !dur24h.Contains(ago12h) {
  207. t.Fatalf(`expect: window "24h" to contain 12 hours ago; actual: %s doesn't contain %s`, dur24h, ago12h)
  208. }
  209. if dur24h.Contains(ago36h) {
  210. t.Fatalf(`expect: window "24h" to not contain 36 hours ago; actual: %s contains %s`, dur24h, ago36h)
  211. }
  212. // "2d" should have 2 day duration and not produce an error
  213. dur2d, err := ParseWindowUTC("2d")
  214. if err != nil {
  215. t.Fatalf(`unexpected error parsing "2d": %s`, err)
  216. }
  217. if dur2d.Duration().Hours() != 48 {
  218. t.Fatalf(`expect: window "2d" to have duration 48 hour; actual: %f hours`, dur2d.Duration().Hours())
  219. }
  220. if !dur2d.Contains(ago36h) {
  221. t.Fatalf(`expect: window "2d" to contain 36 hours ago; actual: %s doesn't contain %s`, dur2d, ago36h)
  222. }
  223. if dur2d.Contains(ago60h) {
  224. t.Fatalf(`expect: window "2d" to not contain 60 hours ago; actual: %s contains %s`, dur2d, ago60h)
  225. }
  226. // "24h offset 14h" should have 24 hour duration and not produce an error
  227. dur24hOff14h, err := ParseWindowUTC("24h offset 14h")
  228. if err != nil {
  229. t.Fatalf(`unexpected error parsing "24h offset 14h": %s`, err)
  230. }
  231. if dur24hOff14h.Duration().Hours() != 24 {
  232. t.Fatalf(`expect: window "24h offset 14h" to have duration 24 hour; actual: %f hours`, dur24hOff14h.Duration().Hours())
  233. }
  234. if dur24hOff14h.Contains(ago12h) {
  235. t.Fatalf(`expect: window "24h offset 14h" not to contain 12 hours ago; actual: %s contains %s`, dur24hOff14h, ago12h)
  236. }
  237. if !dur24hOff14h.Contains(ago36h) {
  238. t.Fatalf(`expect: window "24h offset 14h" to contain 36 hours ago; actual: %s does not contain %s`, dur24hOff14h, ago36h)
  239. }
  240. april152020, _ := time.Parse(time.RFC3339, "2020-04-15T00:00:00Z")
  241. april102020, _ := time.Parse(time.RFC3339, "2020-04-10T00:00:00Z")
  242. april052020, _ := time.Parse(time.RFC3339, "2020-04-05T00:00:00Z")
  243. // "2020-04-08T00:00:00Z,2020-04-12T00:00:00Z" should have 96 hour duration and not produce an error
  244. april8to12, err := ParseWindowUTC("2020-04-08T00:00:00Z,2020-04-12T00:00:00Z")
  245. if err != nil {
  246. t.Fatalf(`unexpected error parsing "2020-04-08T00:00:00Z,2020-04-12T00:00:00Z": %s`, err)
  247. }
  248. if april8to12.Duration().Hours() != 96 {
  249. t.Fatalf(`expect: window %s to have duration 96 hour; actual: %f hours`, april8to12, april8to12.Duration().Hours())
  250. }
  251. if !april8to12.Contains(april102020) {
  252. t.Fatalf(`expect: window April 8-12 to contain April 10; actual: %s doesn't contain %s`, april8to12, april102020)
  253. }
  254. if april8to12.Contains(april052020) {
  255. t.Fatalf(`expect: window April 8-12 to not contain April 5; actual: %s contains %s`, april8to12, april052020)
  256. }
  257. if april8to12.Contains(april152020) {
  258. t.Fatalf(`expect: window April 8-12 to not contain April 15; actual: %s contains %s`, april8to12, april152020)
  259. }
  260. march152020, _ := time.Parse(time.RFC3339, "2020-03-15T00:00:00Z")
  261. march102020, _ := time.Parse(time.RFC3339, "2020-03-10T00:00:00Z")
  262. march052020, _ := time.Parse(time.RFC3339, "2020-03-05T00:00:00Z")
  263. // "1583712000,1583884800" should have 48 hour duration and not produce an error
  264. march9to11, err := ParseWindowUTC("1583712000,1583884800")
  265. if err != nil {
  266. t.Fatalf(`unexpected error parsing "2020-04-08T00:00:00Z,2020-04-12T00:00:00Z": %s`, err)
  267. }
  268. if march9to11.Duration().Hours() != 48 {
  269. t.Fatalf(`expect: window %s to have duration 48 hour; actual: %f hours`, march9to11, march9to11.Duration().Hours())
  270. }
  271. if !march9to11.Contains(march102020) {
  272. t.Fatalf(`expect: window March 9-11 to contain March 10; actual: %s doesn't contain %s`, march9to11, march102020)
  273. }
  274. if march9to11.Contains(march052020) {
  275. t.Fatalf(`expect: window March 9-11 to not contain March 5; actual: %s contains %s`, march9to11, march052020)
  276. }
  277. if march9to11.Contains(march152020) {
  278. t.Fatalf(`expect: window March 9-11 to not contain March 15; actual: %s contains %s`, march9to11, march152020)
  279. }
  280. }
  281. func TestParseWindowWithOffsetString(t *testing.T) {
  282. // ParseWindowWithOffsetString should equal ParseWindowUTC when location == "UTC"
  283. // for all window string formats
  284. todayUTC, err := ParseWindowUTC("today")
  285. if err != nil {
  286. t.Fatalf(`unexpected error parsing "today": %s`, err)
  287. }
  288. todayTZ, err := ParseWindowWithOffsetString("today", "UTC")
  289. if err != nil {
  290. t.Fatalf(`unexpected error parsing "today": %s`, err)
  291. }
  292. if !todayUTC.ApproximatelyEqual(todayTZ, time.Millisecond) {
  293. t.Fatalf(`expect: window "today" UTC to equal "today" with timezone "UTC"; actual: %s not equal %s`, todayUTC, todayTZ)
  294. }
  295. yesterdayUTC, err := ParseWindowUTC("yesterday")
  296. if err != nil {
  297. t.Fatalf(`unexpected error parsing "yesterday": %s`, err)
  298. }
  299. yesterdayTZ, err := ParseWindowWithOffsetString("yesterday", "UTC")
  300. if err != nil {
  301. t.Fatalf(`unexpected error parsing "yesterday": %s`, err)
  302. }
  303. if !yesterdayUTC.ApproximatelyEqual(yesterdayTZ, time.Millisecond) {
  304. t.Fatalf(`expect: window "yesterday" UTC to equal "yesterday" with timezone "UTC"; actual: %s not equal %s`, yesterdayUTC, yesterdayTZ)
  305. }
  306. weekUTC, err := ParseWindowUTC("week")
  307. if err != nil {
  308. t.Fatalf(`unexpected error parsing "week": %s`, err)
  309. }
  310. weekTZ, err := ParseWindowWithOffsetString("week", "UTC")
  311. if err != nil {
  312. t.Fatalf(`unexpected error parsing "week": %s`, err)
  313. }
  314. if !weekUTC.ApproximatelyEqual(weekTZ, time.Millisecond) {
  315. t.Fatalf(`expect: window "week" UTC to equal "week" with timezone "UTC"; actual: %s not equal %s`, weekUTC, weekTZ)
  316. }
  317. monthUTC, err := ParseWindowUTC("month")
  318. if err != nil {
  319. t.Fatalf(`unexpected error parsing "month": %s`, err)
  320. }
  321. monthTZ, err := ParseWindowWithOffsetString("month", "UTC")
  322. if err != nil {
  323. t.Fatalf(`unexpected error parsing "month": %s`, err)
  324. }
  325. if !monthUTC.ApproximatelyEqual(monthTZ, time.Millisecond) {
  326. t.Fatalf(`expect: window "month" UTC to equal "month" with timezone "UTC"; actual: %s not equal %s`, monthUTC, monthTZ)
  327. }
  328. lastweekUTC, err := ParseWindowUTC("lastweek")
  329. if err != nil {
  330. t.Fatalf(`unexpected error parsing "lastweek": %s`, err)
  331. }
  332. lastweekTZ, err := ParseWindowWithOffsetString("lastweek", "UTC")
  333. if err != nil {
  334. t.Fatalf(`unexpected error parsing "lastweek": %s`, err)
  335. }
  336. if !lastweekUTC.ApproximatelyEqual(lastweekTZ, time.Millisecond) {
  337. t.Fatalf(`expect: window "lastweek" UTC to equal "lastweek" with timezone "UTC"; actual: %s not equal %s`, lastweekUTC, lastweekTZ)
  338. }
  339. lastmonthUTC, err := ParseWindowUTC("lastmonth")
  340. if err != nil {
  341. t.Fatalf(`unexpected error parsing "lastmonth": %s`, err)
  342. }
  343. lastmonthTZ, err := ParseWindowWithOffsetString("lastmonth", "UTC")
  344. if err != nil {
  345. t.Fatalf(`unexpected error parsing "lastmonth": %s`, err)
  346. }
  347. if !lastmonthUTC.ApproximatelyEqual(lastmonthTZ, time.Millisecond) {
  348. t.Fatalf(`expect: window "lastmonth" UTC to equal "lastmonth" with timezone "UTC"; actual: %s not equal %s`, lastmonthUTC, lastmonthTZ)
  349. }
  350. dur10mUTC, err := ParseWindowUTC("10m")
  351. if err != nil {
  352. t.Fatalf(`unexpected error parsing "10m": %s`, err)
  353. }
  354. dur10mTZ, err := ParseWindowWithOffsetString("10m", "UTC")
  355. if err != nil {
  356. t.Fatalf(`unexpected error parsing "10m": %s`, err)
  357. }
  358. if !dur10mUTC.ApproximatelyEqual(dur10mTZ, time.Millisecond) {
  359. t.Fatalf(`expect: window "10m" UTC to equal "10m" with timezone "UTC"; actual: %s not equal %s`, dur10mUTC, dur10mTZ)
  360. }
  361. dur24hUTC, err := ParseWindowUTC("24h")
  362. if err != nil {
  363. t.Fatalf(`unexpected error parsing "24h": %s`, err)
  364. }
  365. dur24hTZ, err := ParseWindowWithOffsetString("24h", "UTC")
  366. if err != nil {
  367. t.Fatalf(`unexpected error parsing "24h": %s`, err)
  368. }
  369. if !dur24hUTC.ApproximatelyEqual(dur24hTZ, time.Millisecond) {
  370. t.Fatalf(`expect: window "24h" UTC to equal "24h" with timezone "UTC"; actual: %s not equal %s`, dur24hUTC, dur24hTZ)
  371. }
  372. dur37dUTC, err := ParseWindowUTC("37d")
  373. if err != nil {
  374. t.Fatalf(`unexpected error parsing "37d": %s`, err)
  375. }
  376. dur37dTZ, err := ParseWindowWithOffsetString("37d", "UTC")
  377. if err != nil {
  378. t.Fatalf(`unexpected error parsing "37d": %s`, err)
  379. }
  380. if !dur37dUTC.ApproximatelyEqual(dur37dTZ, time.Millisecond) {
  381. t.Fatalf(`expect: window "37d" UTC to equal "37d" with timezone "UTC"; actual: %s not equal %s`, dur37dUTC, dur37dTZ)
  382. }
  383. // ParseWindowWithOffsetString should be the correct relative to ParseWindowUTC; i.e.
  384. // - for durations, the times should match, but the representations should differ
  385. // by the number of hours offset
  386. // - for words like "today" and "yesterday", the times may not match, in which
  387. // case, for example, "today" UTC-08:00 might equal "yesterday" UTC
  388. // fmtWindow only compares date and time to the minute, not second or
  389. // timezone. Helper for comparing timezone shifted windows.
  390. fmtWindow := func(w Window) string {
  391. s := "nil"
  392. if w.start != nil {
  393. s = w.start.Format("2006-01-02T15:04")
  394. }
  395. e := "nil"
  396. if w.end != nil {
  397. e = w.end.Format("2006-01-02T15:04")
  398. }
  399. return fmt.Sprintf("[%s, %s]", s, e)
  400. }
  401. // Test UTC-08:00 (California), UTC+03:00 (Moscow), UTC+12:00 (New Zealand), and UTC itself
  402. for _, offsetHrs := range []int{-8, 3, 12, 0} {
  403. offStr := fmt.Sprintf("+%02d:00", offsetHrs)
  404. if offsetHrs < 0 {
  405. offStr = fmt.Sprintf("-%02d:00", -offsetHrs)
  406. }
  407. off := time.Duration(offsetHrs) * time.Hour
  408. dur10mTZ, err = ParseWindowWithOffsetString("10m", offStr)
  409. if err != nil {
  410. t.Fatalf(`unexpected error parsing "10m": %s`, err)
  411. }
  412. if !dur10mTZ.ApproximatelyEqual(dur10mUTC, time.Second) {
  413. t.Fatalf(`expect: window "10m" UTC to equal "10m" with timezone "%s"; actual: %s not equal %s`, offStr, dur10mUTC, dur10mTZ)
  414. }
  415. if fmtWindow(dur10mTZ.Shift(-off)) != fmtWindow(dur10mUTC) {
  416. t.Fatalf(`expect: date, hour, and minute of window "10m" UTC to equal that of "10m" %s shifted by %s; actual: %s not equal %s`, offStr, off, fmtWindow(dur10mUTC), fmtWindow(dur10mTZ.Shift(-off)))
  417. }
  418. dur24hTZ, err = ParseWindowWithOffsetString("24h", offStr)
  419. if err != nil {
  420. t.Fatalf(`unexpected error parsing "24h": %s`, err)
  421. }
  422. if !dur24hTZ.ApproximatelyEqual(dur24hUTC, time.Second) {
  423. t.Fatalf(`expect: window "24h" UTC to equal "24h" with timezone "%s"; actual: %s not equal %s`, offStr, dur24hUTC, dur24hTZ)
  424. }
  425. if fmtWindow(dur24hTZ.Shift(-off)) != fmtWindow(dur24hUTC) {
  426. t.Fatalf(`expect: date, hour, and minute of window "24h" UTC to equal that of "24h" %s shifted by %s; actual: %s not equal %s`, offStr, off, fmtWindow(dur24hUTC), fmtWindow(dur24hTZ.Shift(-off)))
  427. }
  428. dur37dTZ, err = ParseWindowWithOffsetString("37d", offStr)
  429. if err != nil {
  430. t.Fatalf(`unexpected error parsing "37d": %s`, err)
  431. }
  432. if !dur37dTZ.ApproximatelyEqual(dur37dUTC, time.Second) {
  433. t.Fatalf(`expect: window "37d" UTC to equal "37d" with timezone "%s"; actual: %s not equal %s`, offStr, dur37dUTC, dur37dTZ)
  434. }
  435. if fmtWindow(dur37dTZ.Shift(-off)) != fmtWindow(dur37dUTC) {
  436. t.Fatalf(`expect: date, hour, and minute of window "37d" UTC to equal that of "37d" %s shifted by %s; actual: %s not equal %s`, offStr, off, fmtWindow(dur37dUTC), fmtWindow(dur37dTZ.Shift(-off)))
  437. }
  438. // "today" and "yesterday" should comply with the current day in each
  439. // respective timezone, depending on if it is ahead of, equal to, or
  440. // behind UTC at the given moment.
  441. todayTZ, err = ParseWindowWithOffsetString("today", offStr)
  442. if err != nil {
  443. t.Fatalf(`unexpected error parsing "today": %s`, err)
  444. }
  445. yesterdayTZ, err = ParseWindowWithOffsetString("yesterday", offStr)
  446. if err != nil {
  447. t.Fatalf(`unexpected error parsing "yesterday": %s`, err)
  448. }
  449. hoursSinceYesterdayUTC := time.Now().UTC().Sub(time.Now().UTC().Truncate(24.0 * time.Hour)).Hours()
  450. hoursUntilTomorrowUTC := 24.0 - hoursSinceYesterdayUTC
  451. aheadOfUTC := float64(offsetHrs)-hoursUntilTomorrowUTC > 0
  452. behindUTC := float64(offsetHrs)+hoursSinceYesterdayUTC < 0
  453. // yesterday in this timezone should equal today UTC
  454. if aheadOfUTC {
  455. if fmtWindow(yesterdayTZ) != fmtWindow(todayUTC) {
  456. t.Fatalf(`expect: window "today" UTC to equal "yesterday" with timezone "%s"; actual: %s not equal %s`, offStr, yesterdayTZ, todayUTC)
  457. }
  458. }
  459. // today in this timezone should equal yesterday UTC
  460. if behindUTC {
  461. if fmtWindow(todayTZ) != fmtWindow(yesterdayUTC) {
  462. t.Fatalf(`expect: window "today" UTC to equal "yesterday" with timezone "%s"; actual: %s not equal %s`, offStr, todayTZ, yesterdayUTC)
  463. }
  464. }
  465. // today in this timezone should equal today UTC, likewise for yesterday
  466. if !aheadOfUTC && !behindUTC {
  467. if fmtWindow(todayTZ) != fmtWindow(todayUTC) {
  468. t.Fatalf(`expect: window "today" UTC to equal "today" with timezone "%s"; actual: %s not equal %s`, offStr, todayTZ, todayUTC)
  469. }
  470. // yesterday in this timezone should equal yesterday UTC
  471. if fmtWindow(yesterdayTZ) != fmtWindow(yesterdayUTC) {
  472. t.Fatalf(`expect: window "yesterday" UTC to equal "yesterday" with timezone "%s"; actual: %s not equal %s`, offStr, yesterdayTZ, yesterdayUTC)
  473. }
  474. }
  475. }
  476. }
  477. // TODO niko/etl
  478. // func TestWindow_Contains(t *testing.T) {}
  479. // TODO niko/etl
  480. // func TestWindow_Duration(t *testing.T) {}
  481. // TODO niko/etl
  482. // func TestWindow_End(t *testing.T) {}
  483. // TODO niko/etl
  484. // func TestWindow_Equal(t *testing.T) {}
  485. // TODO niko/etl
  486. // func TestWindow_ExpandStart(t *testing.T) {}
  487. // TODO niko/etl
  488. // func TestWindow_ExpandEnd(t *testing.T) {}
  489. // TODO niko/etl
  490. // func TestWindow_Start(t *testing.T) {}
  491. // TODO niko/etl
  492. // func TestWindow_String(t *testing.T) {}
  493. func TestWindow_DurationOffsetStrings(t *testing.T) {
  494. w, err := ParseWindowUTC("1d")
  495. if err != nil {
  496. t.Fatalf(`unexpected error parsing "1d": %s`, err)
  497. }
  498. dur, off := w.DurationOffsetStrings()
  499. if dur != "1d" {
  500. t.Fatalf(`expect: window to be "1d"; actual: "%s"`, dur)
  501. }
  502. if off != "" {
  503. t.Fatalf(`expect: offset to be ""; actual: "%s"`, off)
  504. }
  505. w, err = ParseWindowUTC("3h")
  506. if err != nil {
  507. t.Fatalf(`unexpected error parsing "1d": %s`, err)
  508. }
  509. dur, off = w.DurationOffsetStrings()
  510. if dur != "3h" {
  511. t.Fatalf(`expect: window to be "3h"; actual: "%s"`, dur)
  512. }
  513. if off != "" {
  514. t.Fatalf(`expect: offset to be ""; actual: "%s"`, off)
  515. }
  516. w, err = ParseWindowUTC("10m")
  517. if err != nil {
  518. t.Fatalf(`unexpected error parsing "1d": %s`, err)
  519. }
  520. dur, off = w.DurationOffsetStrings()
  521. if dur != "10m" {
  522. t.Fatalf(`expect: window to be "10m"; actual: "%s"`, dur)
  523. }
  524. if off != "" {
  525. t.Fatalf(`expect: offset to be ""; actual: "%s"`, off)
  526. }
  527. w, err = ParseWindowUTC("1589448338,1589534798")
  528. if err != nil {
  529. t.Fatalf(`unexpected error parsing "1589448338,1589534798": %s`, err)
  530. }
  531. dur, off = w.DurationOffsetStrings()
  532. if dur != "1441m" {
  533. t.Fatalf(`expect: window to be "1441m"; actual: "%s"`, dur)
  534. }
  535. if off == "" {
  536. t.Fatalf(`expect: offset to not be ""; actual: "%s"`, off)
  537. }
  538. w, err = ParseWindowUTC("yesterday")
  539. if err != nil {
  540. t.Fatalf(`unexpected error parsing "1589448338,1589534798": %s`, err)
  541. }
  542. dur, off = w.DurationOffsetStrings()
  543. if dur != "1d" {
  544. t.Fatalf(`expect: window to be "1d"; actual: "%s"`, dur)
  545. }
  546. }