Skip to content

Commit

Permalink
Merge ffa5482 into ea1afbe
Browse files Browse the repository at this point in the history
  • Loading branch information
servingUpAces committed Jan 10, 2017
2 parents ea1afbe + ffa5482 commit 562f48f
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 43 deletions.
54 changes: 29 additions & 25 deletions app/dao/notifications_dao.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@

import pytz
from datetime import (
datetime,
timedelta,
date
)
from itertools import groupby
date)

from flask import current_app
from werkzeug.datastructures import MultiDict
from sqlalchemy import (desc, func, or_, and_, asc)
from sqlalchemy import (desc, func, or_, and_, asc, extract)
from sqlalchemy.orm import joinedload

from app import db, create_uuid
Expand Down Expand Up @@ -216,24 +215,31 @@ def get_notifications_for_job(service_id, job_id, filter_dict=None, page=1, page
def get_notification_billable_unit_count_per_month(service_id, year):
start, end = get_financial_year(year)

"""
The query needs to sum the billable_units per month, but this needs to be the month in BST (British Standard Time).
The database stores all timestamps as UTC without the timezone.
- First set the timezone on created_at to UTC
- then convert the timezone to BST (or Europe/London)
- lastly truncate the datetime to month to group the sum of the billable_units
"""
month = func.date_trunc("month",
func.timezone("Europe/London", func.timezone("UTC",
NotificationHistory.created_at)))
notifications = db.session.query(
NotificationHistory.created_at,
NotificationHistory.billable_units
).order_by(
NotificationHistory.created_at
month,
func.sum(NotificationHistory.billable_units)
).filter(
NotificationHistory.billable_units != 0,
NotificationHistory.service_id == service_id,
NotificationHistory.created_at >= start,
NotificationHistory.created_at < end
)
).group_by(
month
).order_by(
month
).all()

return [
(month, sum(count for _, count in row))
for month, row in groupby(
notifications, lambda row: get_bst_month(row[0])
)
]
return [(datetime.strftime(x[0], "%B"), x[1]) for x in notifications]


@statsd(namespace="dao")
Expand Down Expand Up @@ -384,13 +390,11 @@ def get_financial_year(year):


def get_april_fools(year):
return datetime(
year, 4, 1, 0, 0, 0, 0,
pytz.timezone("Europe/London")
).astimezone(pytz.utc)


def get_bst_month(datetime):
return pytz.utc.localize(datetime).astimezone(
pytz.timezone("Europe/London")
).strftime('%B')
"""
This function converts the start of the financial year April 1, 00:00 as BST (British Standard Time) to UTC,
the tzinfo is lastly removed from the datetime becasue the database stores the timestamps without timezone.
:param year: the year to calculate the April 1, 00:00 BST for
:return: the datetime of April 1 for the given year, for example 2016 = 2016-03-31 23:00:00
"""
return pytz.timezone('Europe/London').localize(datetime(year, 4, 1, 0, 0, 0)).astimezone(pytz.UTC).replace(
tzinfo=None)
43 changes: 25 additions & 18 deletions tests/app/dao/test_notification_dao.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
update_notification_status_by_reference,
dao_delete_notifications_and_history_by_id,
dao_timeout_notifications,
get_financial_year)
get_financial_year,
get_april_fools)

from tests.app.conftest import (sample_notification, sample_template, sample_email_template, sample_service, sample_job,
sample_api_key)
Expand Down Expand Up @@ -700,21 +701,23 @@ def test_get_all_notifications_for_job_by_status(notify_db, notify_db_session, s

def test_get_notification_billable_unit_count_per_month(notify_db, notify_db_session, sample_service):

for year, month, day in (
(2017, 1, 15), # ↓ 2016 financial year
(2016, 8, 1),
(2016, 7, 15),
(2016, 4, 15),
(2016, 4, 15),
(2016, 4, 1), # ↓ 2015 financial year
(2016, 3, 31),
(2016, 1, 15)
for year, month, day, hour, minute, second in (
(2017, 1, 15, 23, 59, 59), # ↓ 2016 financial year
(2016, 9, 30, 23, 59, 59), # counts in October with BST conversion
(2016, 6, 30, 23, 50, 20),
(2016, 7, 15, 9, 20, 25),
(2016, 4, 1, 1, 1, 00),
(2016, 4, 1, 0, 0, 00),
(2016, 3, 31, 23, 00, 1), # counts in April with BST conversion
(2015, 4, 1, 13, 8, 59), # ↓ 2015 financial year
(2015, 11, 20, 22, 40, 45),
(2016, 1, 31, 23, 30, 40) # counts in January no BST conversion in winter
):
sample_notification(
notify_db, notify_db_session, service=sample_service,
created_at=datetime(
year, month, day, 0, 0, 0, 0
) - timedelta(hours=1, seconds=1) # one second before midnight
year, month, day, hour, minute, second, 0
)
)

for financial_year, months in (
Expand All @@ -724,11 +727,11 @@ def test_get_notification_billable_unit_count_per_month(notify_db, notify_db_ses
),
(
2016,
[('April', 2), ('July', 2), ('January', 1)]
[('April', 3), ('July', 2), ('October', 1), ('January', 1)]
),
(
2015,
[('January', 1), ('March', 2)]
[('April', 1), ('November', 1), ('January', 1)]
),
(
2014,
Expand Down Expand Up @@ -1201,7 +1204,11 @@ def test_should_exclude_test_key_notifications_by_default(

def test_get_financial_year():
start, end = get_financial_year(2000)
assert start.tzinfo == pytz.utc
assert start.isoformat() == '2000-04-01T00:01:00+00:00'
assert end.tzinfo == pytz.utc
assert end.isoformat() == '2001-04-01T00:01:00+00:00'
assert str(start) == '2000-03-31 23:00:00'
assert str(end) == '2001-03-31 23:00:00'


def test_get_april_fools():
april_fools = get_april_fools(2016)
assert str(april_fools) == '2016-03-31 23:00:00'
assert april_fools.tzinfo is None

0 comments on commit 562f48f

Please sign in to comment.