github.com/rohankumardubey/aresdb@v0.0.2-0.20190517170215-e54e3ca06b9c/query/time_filter.go (about) 1 // Copyright (c) 2017-2018 Uber Technologies, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package query 16 17 import ( 18 "github.com/uber/aresdb/query/expr" 19 "github.com/uber/aresdb/utils" 20 "strconv" 21 "strings" 22 "time" 23 ) 24 25 var timeUnitMap = map[string]string{ 26 "year": "y", 27 "quarter": "q", 28 "month": "M", 29 "week": "w", 30 "day": "d", 31 "hour": "h", 32 "quarter-hour": "15m", 33 "minute": "m", 34 "second": "s", 35 } 36 37 // Time that is calendar aligned to the unit. 38 type alignedTime struct { 39 Time time.Time `json:"time"` 40 // Values for unit: y, q, M, w, d, {12, 8, 6, 4, 3, 2}h, h, {30, 20, 15, 12, 10, 6, 5, 4, 3, 2}m, m 41 Unit string `json:"unit"` 42 } 43 44 // adjustMidnight adjusts daylight saving anomalies in a few timezones 45 // that return day boundary/midnight as either 23:00 (of the previous day) or 01:00. 46 // 47 // Fix for America/Sao_Paulo daylight saving starts (2016-10-16): 48 // The midnight of 2016-10-16 does not exist and time.Date returns 23:00 of the previous day. 49 // 50 // Must check whether the 1 hour rewind still gives the same day: 51 // For Asia/Beirut, this is not true for 2017-03-26, and true for 2017-03-27 and beyond. 52 func adjustMidnight(t time.Time) time.Time { 53 if t.Hour() == 23 { 54 // Add one hour from 23:00 to 01:00 on the transition day; 55 // and from 23:00 to 00:00 on non-transition days. 56 return t.Add(time.Hour) 57 } else if t.Hour() == 1 { 58 t2 := t.Add(-time.Hour) 59 if t2.Day() == t.Day() { 60 // Must check whether the 1 hour rewind still gives the same day: 61 // For Asia/Beirut, this is false for 2017-03-26 (transition day), and true for 2017-03-27. 62 return t2 63 } 64 } 65 return t 66 } 67 68 func parseTimezone(timezone string) (*time.Location, error) { 69 segments := strings.Split(timezone, ":") 70 hours, err := strconv.Atoi(segments[0]) 71 if err == nil { 72 minutes := 0 73 if len(segments) > 1 { 74 minutes, err = strconv.Atoi(segments[1]) 75 } 76 if err == nil { 77 if hours < 0 { 78 minutes = -minutes 79 } 80 return time.FixedZone(timezone, hours*60*60+minutes*60), nil 81 } 82 } 83 return time.LoadLocation(timezone) 84 } 85 86 // GetCurrentCalendarUnit returns the start and end of the calendar unit for base. 87 func GetCurrentCalendarUnit(base time.Time, unit string) (start, end time.Time, err error) { 88 return applyTimeOffset(base, 0, unit) 89 } 90 91 // Returns the start and end of the calendar `unit` that is `amount` `unit`s later from `base`. 92 func applyTimeOffset(base time.Time, amount int, unit string) (start, end time.Time, err error) { 93 monthStart := time.Date(base.Year(), base.Month(), 1, 0, 0, 0, 0, base.Location()) 94 monthStart = adjustMidnight(monthStart) 95 dayStart := time.Date(base.Year(), base.Month(), base.Day(), 0, 0, 0, 0, base.Location()) 96 dayStart = adjustMidnight(dayStart) 97 switch unit { 98 case "y": 99 start = time.Date(base.Year()+amount, time.January, 1, 0, 0, 0, 0, base.Location()) 100 end = time.Date(base.Year()+1+amount, time.January, 1, 0, 0, 0, 0, base.Location()) 101 start = adjustMidnight(start) 102 end = adjustMidnight(end) 103 case "q": 104 start = monthStart.AddDate(0, (1-int(base.Month()))%3+3*amount, 0) 105 end = start.AddDate(0, 3, 0) 106 start = adjustMidnight(start) 107 end = adjustMidnight(end) 108 case "M": 109 start = monthStart.AddDate(0, amount, 0) 110 end = start.AddDate(0, 1, 0) 111 start = adjustMidnight(start) 112 end = adjustMidnight(end) 113 case "w": 114 start = dayStart.AddDate(0, 0, (-int(base.Weekday())-6)%7+7*amount) 115 end = start.AddDate(0, 0, 7) 116 start = adjustMidnight(start) 117 end = adjustMidnight(end) 118 case "d": 119 start = dayStart.AddDate(0, 0, amount) 120 end = start.AddDate(0, 0, 1) 121 start = adjustMidnight(start) 122 end = adjustMidnight(end) 123 case "h": 124 // Round to hour. 125 base = time.Date(base.Year(), base.Month(), base.Day(), base.Hour(), 0, 0, 0, base.Location()) 126 // Apply the offset. 127 start = base.Add(time.Duration(amount) * time.Hour) 128 end = start.Add(time.Hour) 129 case "15m": 130 // Round to quarter-hour. 131 base = time.Date(base.Year(), base.Month(), base.Day(), base.Hour(), base.Minute()-base.Minute()%15, 0, 0, base.Location()) 132 // Apply the offset. 133 start = base.Add(time.Duration(amount) * time.Minute * 15) 134 end = start.Add(time.Minute * 15) 135 case "m": 136 // Round to minute. 137 base = time.Date(base.Year(), base.Month(), base.Day(), base.Hour(), base.Minute(), 0, 0, base.Location()) 138 // Apply the offset. 139 start = base.Add(time.Duration(amount) * time.Minute) 140 end = start.Add(time.Minute) 141 default: 142 err = utils.StackError(nil, "Unknown time filter unit: %s", unit) 143 } 144 return 145 } 146 147 // Returns the start and end of the absolute calendar unit specified in `dateExpr` and `timeExpr`. 148 func parseAbsoluteTime(dateExpr, timeExpr string, location *time.Location) (start, end time.Time, unit string, err error) { 149 var year, quarter, hour, minute int 150 month, day := time.January, 1 151 152 segments := strings.Split(dateExpr, "-") 153 if len(segments) > 3 { 154 err = utils.StackError(nil, "Unknown time expression: %s %s", dateExpr, timeExpr) 155 return 156 } 157 158 year, err = strconv.Atoi(segments[0]) 159 if err != nil { 160 err = utils.StackError(err, "failed to parse %s as year", segments[0]) 161 return 162 } 163 unit = "y" 164 165 if len(segments) >= 2 { 166 if segments[1][0] == 'Q' { 167 quarter, err = strconv.Atoi(segments[1][1:]) 168 if err != nil { 169 err = utils.StackError(err, "failed to parse %s as quarter", segments[1][1:]) 170 return 171 } 172 if len(segments) == 3 { 173 err = utils.StackError(nil, "Unknown time expression: %s %s", dateExpr, timeExpr) 174 return 175 } 176 month = time.January + time.Month(quarter-1)*3 177 unit = "q" 178 } else { 179 var monthNumber int 180 monthNumber, err = strconv.Atoi(segments[1]) 181 if err != nil { 182 err = utils.StackError(err, "failed to parse %s as month", segments[1]) 183 return 184 } 185 month = time.Month(monthNumber) 186 unit = "M" 187 } 188 } 189 190 if len(segments) == 3 { 191 day, err = strconv.Atoi(segments[2]) 192 if err != nil { 193 err = utils.StackError(err, "failed to parse %s as day", segments[2]) 194 return 195 } 196 unit = "d" 197 } else if timeExpr != "" { 198 err = utils.StackError(nil, "Unknown time expression: %s %s", dateExpr, timeExpr) 199 return 200 } 201 202 if timeExpr != "" { 203 segments = strings.Split(timeExpr, ":") 204 if len(segments) > 2 { 205 err = utils.StackError(nil, "Unknown time expression: %s %s", dateExpr, timeExpr) 206 return 207 } 208 209 hour, err = strconv.Atoi(segments[0]) 210 if err != nil { 211 err = utils.StackError(err, "failed to parse %s as hour", segments[0]) 212 return 213 } 214 unit = "h" 215 216 if len(segments) == 2 { 217 minute, err = strconv.Atoi(segments[1]) 218 if err != nil { 219 err = utils.StackError(err, "failed to parse %s as minute", segments[1]) 220 return 221 } 222 unit = "m" 223 224 // Temporary hack until summary switch to use relative time expression. 225 if minute%15 == 0 { 226 unit = "15m" 227 } 228 } 229 } 230 231 t := time.Date(year, month, day, hour, minute, 0, 0, location) 232 if hour == 0 { 233 t = adjustMidnight(t) 234 } 235 start, end, err = applyTimeOffset(t, 0, unit) 236 return 237 } 238 239 // Returns the start and end of the calendar unit specified in `expression`. 240 func parseTimeFilterExpression(expression string, now time.Time) (start, end time.Time, unit string, err error) { 241 start, end = now, now 242 unit = "m" 243 if expression == "now" { 244 unit = "s" 245 return 246 } 247 248 if expression == "today" { 249 expression = "this day" 250 } else if expression == "yesterday" { 251 expression = "last day" 252 } 253 254 var amount int 255 segments := strings.Split(expression, " ") 256 if segments[0] == "this" { 257 if len(segments) != 2 { 258 err = utils.StackError(nil, "Unknown time filter expression: %s", expression) 259 return 260 } 261 unit = timeUnitMap[segments[1]] 262 if unit == "" { 263 err = utils.StackError(nil, "Unknown time filter unit: %s", segments[1]) 264 } 265 start, end, err = applyTimeOffset(now, 0, unit) 266 return 267 } else if segments[0] == "last" { 268 if len(segments) != 2 { 269 err = utils.StackError(nil, "Unknown time filter expression: %s", expression) 270 return 271 } 272 unit = timeUnitMap[segments[1]] 273 if unit == "" { 274 err = utils.StackError(nil, "Unknown time filter unit: %s", segments[1]) 275 } 276 start, end, err = applyTimeOffset(now, -1, unit) 277 return 278 } else if segments[len(segments)-1] == "ago" { 279 if len(segments) != 3 { 280 err = utils.StackError(nil, "Unknown time filter expression: %s", expression) 281 return 282 } 283 amount, err = strconv.Atoi(segments[0]) 284 if err != nil { 285 err = utils.StackError(err, "failed to parse %s as a number", segments[0]) 286 return 287 } 288 unit = timeUnitMap[segments[1][:len(segments[1])-1]] 289 if unit == "" { 290 err = utils.StackError(nil, "Unknown time filter unit: %s", segments[1]) 291 } 292 start, end, err = applyTimeOffset(now, -amount, unit) 293 return 294 } else if len(segments) == 1 { 295 amount, err = strconv.Atoi(expression[:len(expression)-1]) 296 if err == nil { 297 unit = expression[len(expression)-1:] 298 start, end, err = applyTimeOffset(now, amount, unit) 299 if err == nil { 300 return 301 } 302 } 303 } 304 305 dateExpr := segments[0] 306 var timeExpr string 307 if len(segments) == 2 { 308 timeExpr = segments[1] 309 } else if len(segments) > 2 { 310 err = utils.StackError(nil, "Unknown time filter expression: %s", expression) 311 return 312 } else if len(segments) == 1 { 313 var seconds int64 314 seconds, err = strconv.ParseInt(segments[0], 10, 64) 315 if seconds > 99999999999 { 316 //we will assume data over 99999999999 will be timestamp in ms, and convert it to be in seconds 317 seconds = seconds / 1000 318 } 319 // Numbers above 9999999 are treated as timestamps, otherwise the corresponding Time object (of year 10000 and beyond) 320 // will fail JSON marshaling, and criples debugz. 321 if err == nil && seconds > 9999999 { 322 t := time.Unix(seconds, 0).In(now.Location()) 323 rounded := t.Round(time.Minute) 324 if rounded.Equal(t) { 325 start = rounded 326 end = rounded 327 unit = "m" 328 } else { 329 start, end = t, t 330 unit = "s" 331 } 332 return 333 } 334 } 335 start, end, unit, err = parseAbsoluteTime(dateExpr, timeExpr, now.Location()) 336 return 337 } 338 339 func parseTimeFilter(filter TimeFilter, loc *time.Location, now time.Time) (from, to *alignedTime, err error) { 340 if loc == nil { 341 loc = time.UTC 342 } 343 now = now.In(loc).Round(time.Second) 344 345 if filter.From != "" { 346 from = &alignedTime{} 347 from.Time, _, from.Unit, err = parseTimeFilterExpression(filter.From, now) 348 if err != nil { 349 err = utils.StackError(err, "failed to parse time filter `from` expression: %s", filter.From) 350 return 351 } 352 } 353 354 if filter.To != "" { 355 to = &alignedTime{} 356 _, to.Time, to.Unit, err = parseTimeFilterExpression(filter.To, now) 357 if err != nil { 358 err = utils.StackError(err, "failed to parse time filter `to` expression: %s", filter.To) 359 return 360 } 361 } else if from != nil { 362 // Populate to with now if from is present. 363 to = &alignedTime{now, "s"} 364 } 365 return 366 } 367 368 func createTimeFilterExpr(expression expr.Expr, from, to *alignedTime) (fromExpr, toExpr expr.Expr) { 369 if from != nil && from.Unit != "" { 370 fromExpr = &expr.BinaryExpr{ 371 ExprType: expr.Boolean, 372 Op: expr.GTE, 373 LHS: expression, 374 RHS: &expr.NumberLiteral{ 375 Int: int(from.Time.Unix()), 376 Expr: strconv.FormatInt(from.Time.Unix(), 10), 377 ExprType: expr.Unsigned, 378 }, 379 } 380 } 381 if to != nil && to.Unit != "" { 382 toExpr = &expr.BinaryExpr{ 383 ExprType: expr.Boolean, 384 Op: expr.LT, 385 LHS: expression, 386 RHS: &expr.NumberLiteral{ 387 Int: int(to.Time.Unix()), 388 Expr: strconv.FormatInt(to.Time.Unix(), 10), 389 ExprType: expr.Unsigned, 390 }, 391 } 392 } 393 return 394 }