Skip to content

Commit

Permalink
Properly resolve absolute time ranges with custom timezones
Browse files Browse the repository at this point in the history
  • Loading branch information
brutasse committed Mar 26, 2014
1 parent 40edd0f commit c8854f5
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 23 deletions.
6 changes: 6 additions & 0 deletions docs/releases.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
Graphite-API releases
=====================

1.0.2 -- **in development**
---------------------------

* Proper timezone handling of ``from`` and ``until`` with client-supplied
timezones (`Graphite-web issue #639 <https://github.com/graphite-project/graphite-web/issues/639>`_).

1.0.1 -- 2014-03-21
-------------------

Expand Down
27 changes: 11 additions & 16 deletions graphite_api/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from .render.attime import parseTimeOffset
from .render.glyph import format_units
from .render.datalib import TimeSeries
from .utils import to_seconds
from .utils import to_seconds, epoch

NAN = float('NaN')
INF = float('inf')
Expand All @@ -40,11 +40,6 @@


# Utility functions
def timestamp(datetime):
"Convert a datetime object into epoch time"
return time.mktime(datetime.timetuple())


not_none = partial(is_not, None)


Expand Down Expand Up @@ -1736,7 +1731,7 @@ def limit(requestContext, seriesList, n):
"""
Takes one metric or a wildcard seriesList followed by an integer N.
Only draw the first N metrics. Useful when testing a wildcard in a
Only draw the first N metrics. Useful when testing a wildcard in a
metric.
Example::
Expand Down Expand Up @@ -2373,8 +2368,8 @@ def constantLine(requestContext, value):
&target=constantLine(123.456)
"""
start = int(timestamp(requestContext['startTime']))
end = int(timestamp(requestContext['endTime']))
start = int(epoch(requestContext['startTime']))
end = int(epoch(requestContext['endTime']))
step = end - start
series = TimeSeries(str(value), start, end, step, [value, value])
return [series]
Expand Down Expand Up @@ -2510,8 +2505,8 @@ def identity(requestContext, name):
where x(t) == t.
"""
step = 60
start = int(time.mktime(requestContext["startTime"].timetuple()))
end = int(time.mktime(requestContext["endTime"].timetuple()))
start = int(epoch(requestContext["startTime"]))
end = int(epoch(requestContext["endTime"]))
values = range(start, end, step)
series = TimeSeries(name, start, end, step, values)
series.pathExpression = 'identity("%s")' % name
Expand Down Expand Up @@ -2989,12 +2984,12 @@ def sinFunction(requestContext, name, amplitude=1):
values = []

while when < requestContext["endTime"]:
values.append(math.sin(time.mktime(when.timetuple()))*amplitude)
values.append(math.sin(epoch(when))*amplitude)
when += delta

series = TimeSeries(
name, int(time.mktime(requestContext["startTime"].timetuple())),
int(time.mktime(requestContext["endTime"].timetuple())),
name, int(epoch(requestContext["startTime"])),
int(epoch(requestContext["endTime"])),
step, values)
series.pathExpression = 'sin({0})'.format(name)
return [series]
Expand Down Expand Up @@ -3025,8 +3020,8 @@ def randomWalkFunction(requestContext, name):
when += delta

return [TimeSeries(
name, int(time.mktime(requestContext["startTime"].timetuple())),
int(time.mktime(requestContext["endTime"].timetuple())),
name, int(epoch(requestContext["startTime"])),
int(epoch(requestContext["endTime"])),
step, values)]


Expand Down
4 changes: 2 additions & 2 deletions graphite_api/render/attime.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def parseATTime(s, tzinfo=None):

def parseTimeReference(ref):
if not ref or ref == 'now':
return datetime.now()
return datetime.utcnow()

# Time-of-day reference
i = ref.find(':')
Expand All @@ -76,7 +76,7 @@ def parseTimeReference(ref):
hour, min = 16, 0
ref = ref[7:]

refDate = datetime.now().replace(hour=hour, minute=min, second=0)
refDate = datetime.utcnow().replace(hour=hour, minute=min, second=0)

# Day reference
if ref in ('yesterday', 'today', 'tomorrow'): # yesterday, today, tomorrow
Expand Down
9 changes: 4 additions & 5 deletions graphite_api/render/datalib.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License."""

import time

from structlog import get_logger

from ..utils import epoch

logger = get_logger()


Expand Down Expand Up @@ -83,8 +82,8 @@ def fetchData(requestContext, pathExpr):
from ..app import app

seriesList = []
startTime = int(time.mktime(requestContext['startTime'].timetuple()))
endTime = int(time.mktime(requestContext['endTime'].timetuple()))
startTime = int(epoch(requestContext['startTime']))
endTime = int(epoch(requestContext['endTime']))

def _fetchData(pathExpr, startTime, endTime, requestContext, seriesList):
matching_nodes = app.store.find(pathExpr, startTime, endTime)
Expand Down
9 changes: 9 additions & 0 deletions graphite_api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License."""
import calendar
import pytz

from flask import request

Expand Down Expand Up @@ -56,3 +58,10 @@ def getlist(self, key):

def to_seconds(delta):
return abs(delta.seconds + delta.days * 86400)


def epoch(dt):
"""
Returns the epoch timestamp of a timezone-aware datetime object.
"""
return calendar.timegm(dt.astimezone(pytz.utc).timetuple())
24 changes: 24 additions & 0 deletions tests/test_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,30 @@ def test_render_constant_line(self):
for point, ts in data:
self.assertEqual(point, 12)

def test_correct_timezone(self):
response = self.app.get(self.url, query_string={
'target': 'constantLine(12)',
'format': 'json',
'from': '07:00_20140226',
'until': '08:00_20140226',
# tz is UTC
})
data = json.loads(response.data.decode('utf-8'))[0]['datapoints']

# all the from/until/tz combinations lead to the same window
expected = [[12, 1393398000], [12, 1393401600]]
self.assertEqual(data, expected)

response = self.app.get(self.url, query_string={
'target': 'constantLine(12)',
'format': 'json',
'from': '08:00_20140226',
'until': '09:00_20140226',
'tz': 'Europe/Berlin',
})
data = json.loads(response.data.decode('utf-8'))[0]['datapoints']
self.assertEqual(data, expected)

def test_render_options(self):
self.create_db()
db2 = os.path.join(WHISPER_DIR, 'foo.wsp')
Expand Down

0 comments on commit c8854f5

Please sign in to comment.