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  }