Skip to content

Commit

Permalink
Merge cefa00b into 46d1e3b
Browse files Browse the repository at this point in the history
  • Loading branch information
servingUpAces committed Jan 10, 2017
2 parents 46d1e3b + cefa00b commit 55a0ec4
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 50 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 @@ -226,24 +225,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 @@ -394,13 +400,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)
58 changes: 33 additions & 25 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 app.dao.services_dao import dao_update_service

Expand Down Expand Up @@ -150,11 +151,11 @@ def test_should_by_able_to_get_template_count_from_notifications_history(notify_


def test_template_history_should_ignore_test_keys(
notify_db,
notify_db_session,
sample_team_api_key,
sample_test_api_key,
sample_api_key
notify_db,
notify_db_session,
sample_team_api_key,
sample_test_api_key,
sample_api_key
):
sms = sample_template(notify_db, notify_db_session)

Expand Down Expand Up @@ -764,28 +765,31 @@ 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 (
(2017, []),
(2016, [('April', 2), ('July', 2), ('January', 1)]),
(2015, [('January', 1), ('March', 2)]),
(2014, [])
(2017, []),
(2016, [('April', 3), ('July', 2), ('October', 1), ('January', 1)]),
(2015, [('April', 1), ('November', 1), ('January', 1)]),
(2014, [])
):
assert get_notification_billable_unit_count_per_month(
sample_service.id, financial_year
Expand Down Expand Up @@ -1294,7 +1298,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 55a0ec4

Please sign in to comment.