diff --git a/app/formatters.py b/app/formatters.py index a410aa076..7f856b764 100644 --- a/app/formatters.py +++ b/app/formatters.py @@ -17,6 +17,7 @@ from notifications_utils.formatters import nl2br as utils_nl2br from notifications_utils.recipients import InvalidPhoneError, validate_phone_number from notifications_utils.take import Take +from app.utils.csv import get_user_preferred_timezone from app.utils.time import parse_naive_dt @@ -31,13 +32,14 @@ def convert_to_boolean(value): def format_datetime(date): - return "{} at {} UTC".format(format_date(date), format_time_24h(date)) + return "{} at {} {}".format( + format_date(date), format_time_24h(date), get_user_preferred_timezone() + ) def format_datetime_24h(date): - return "{} at {} UTC".format( - format_date(date), - format_time_24h(date), + return "{} at {} {}".format( + format_date(date), format_time_24h(date), get_user_preferred_timezone() ) @@ -46,39 +48,50 @@ def format_time(date): def format_datetime_normal(date): - return "{} at {} UTC".format(format_date_normal(date), format_time_24h(date)) + return "{} at {} {}".format( + format_date_normal(date), format_time_24h(date), get_user_preferred_timezone() + ) def format_datetime_short(date): - return "{} at {} UTC".format(format_date_short(date), format_time_24h(date)) + return "{} at {} {}".format( + format_date_short(date), format_time_24h(date), get_user_preferred_timezone() + ) def format_datetime_relative(date): - return "{} at {} UTC".format(get_human_day(date), format_time_24h(date)) + return "{} at {} {}".format( + get_human_day(date), format_time_24h(date), get_user_preferred_timezone() + ) def format_datetime_numeric(date): - return "{} {} UTC".format( - format_date_numeric(date), - format_time_24h(date), + return "{} {} {}".format( + format_date_numeric(date), format_time_24h(date), get_user_preferred_timezone() ) def format_date_numeric(date): date = parse_naive_dt(date) - return date.strftime("%Y-%m-%d") + + et = pytz.timezone(get_user_preferred_timezone()) + return date.replace(tzinfo=timezone.utc).astimezone(et).strftime("%Y-%m-%d") def format_time_24h(date): date = parse_naive_dt(date) - return date.strftime("%H:%M") + + et = pytz.timezone(get_user_preferred_timezone()) + return date.replace(tzinfo=timezone.utc).astimezone(et).strftime("%H:%M") def get_human_day(time, date_prefix=""): # Add 1 minute to transform 00:00 into ‘midnight today’ instead of ‘midnight tomorrow’ time = parse_naive_dt(time) + et = pytz.timezone(get_user_preferred_timezone()) + time = time.replace(tzinfo=timezone.utc).astimezone(et) date = (time - timedelta(minutes=1)).date() - now = datetime.now(pytz.utc) + now = datetime.now(et) if date == (now + timedelta(days=1)).date(): return "tomorrow" @@ -100,7 +113,8 @@ def get_human_day(time, date_prefix=""): def format_date(date): date = parse_naive_dt(date) - return date.strftime("%A %d %B %Y") + et = pytz.timezone(get_user_preferred_timezone()) + return date.replace(tzinfo=timezone.utc).astimezone(et).strftime("%A %d %B %Y") def format_date_normal(date): @@ -110,7 +124,8 @@ def format_date_normal(date): def format_date_short(date): date = parse_naive_dt(date) - return _format_datetime_short(date) + et = pytz.timezone(get_user_preferred_timezone()) + return _format_datetime_short(date.replace(tzinfo=timezone.utc).astimezone(et)) def format_date_human(date): @@ -118,15 +133,17 @@ def format_date_human(date): def format_datetime_human(date, date_prefix=""): - return "{} at {} UTC".format( + return "{} at {} {}".format( get_human_day(date, date_prefix="on"), format_time_24h(date), + get_user_preferred_timezone(), ) def format_day_of_week(date): date = parse_naive_dt(date) - return date.strftime("%A") + et = pytz.timezone(get_user_preferred_timezone()) + return date.replace(tzinfo=timezone.utc).astimezone(et).strftime("%A") def _format_datetime_short(datetime): diff --git a/app/main/forms.py b/app/main/forms.py index 0be785a5e..d21dff168 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -64,15 +64,18 @@ from app.main.validators import ( from app.models.feedback import PROBLEM_TICKET_TYPE, QUESTION_TICKET_TYPE from app.models.organization import Organization from app.utils import branding, merge_jsonlike +from app.utils.csv import get_user_preferred_timezone from app.utils.user_permissions import all_ui_permissions, permission_options def get_time_value_and_label(future_time): + et = pytz.timezone(get_user_preferred_timezone()) return ( - future_time.astimezone(pytz.utc).replace(tzinfo=None).isoformat(), - "{} at {} UTC".format( - get_human_day(future_time.astimezone(pytz.utc)), - get_human_time(future_time.astimezone(pytz.utc)), + future_time.astimezone(et).replace(tzinfo=None).isoformat(), + "{} at {} {}".format( + get_human_day(future_time.astimezone(et)), + get_human_time(future_time.astimezone(et)), + get_user_preferred_timezone(), ), ) @@ -85,21 +88,25 @@ def get_human_time(time): def get_human_day(time, prefix_today_with="T"): # Add 1 hour to get ‘midnight today’ instead of ‘midnight tomorrow’ + + et = pytz.timezone(get_user_preferred_timezone()) time = (time - timedelta(hours=1)).strftime("%A") - if time == datetime.now(pytz.utc).strftime("%A"): + if time == datetime.now(et).strftime("%A"): return "{}oday".format(prefix_today_with) - if time == (datetime.now(pytz.utc) + timedelta(days=1)).strftime("%A"): + if time == (datetime.now(et) + timedelta(days=1)).strftime("%A"): return "Tomorrow" return time def get_furthest_possible_scheduled_time(): # We want local time to find date boundaries - return (datetime.now(pytz.utc) + timedelta(days=4)).replace(hour=0) + et = pytz.timezone(get_user_preferred_timezone()) + return (datetime.now(et) + timedelta(days=4)).replace(hour=0) def get_next_hours_until(until): - now = datetime.now(pytz.utc) + et = pytz.timezone(get_user_preferred_timezone()) + now = datetime.now(et) hours = int((until - now).total_seconds() / (60 * 60)) return [ (now + timedelta(hours=i)).replace(minute=0, second=0, microsecond=0) @@ -108,7 +115,8 @@ def get_next_hours_until(until): def get_next_days_until(until): - now = datetime.now(pytz.utc) + et = pytz.timezone(get_user_preferred_timezone()) + now = datetime.now(et) days = int((until - now).total_seconds() / (60 * 60 * 24)) return [ get_human_day((now + timedelta(days=i)), prefix_today_with="Later t") diff --git a/app/main/views/performance.py b/app/main/views/performance.py index b7dbd00a6..b0037c7cb 100644 --- a/app/main/views/performance.py +++ b/app/main/views/performance.py @@ -8,13 +8,15 @@ from flask import render_template from app import performance_dashboard_api_client, status_api_client from app.main import main +from app.utils.csv import get_user_preferred_timezone @main.route("/performance") def performance(): + et = pytz.timezone(get_user_preferred_timezone()) stats = performance_dashboard_api_client.get_performance_dashboard_stats( - start_date=(datetime.now(pytz.utc) - timedelta(days=7)).date(), - end_date=datetime.now(pytz.utc).date(), + start_date=(datetime.now(et) - timedelta(days=7)).date(), + end_date=datetime.now(et).date(), ) stats["organizations_using_notify"] = sorted( [ @@ -35,4 +37,5 @@ def performance(): stats[ "count_of_live_services_and_organizations" ] = status_api_client.get_count_of_live_services_and_organizations() + return render_template("views/performance.html", **stats) diff --git a/app/models/user.py b/app/models/user.py index 081ee2f6c..abd887a4d 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -47,12 +47,14 @@ class User(JSONModel, UserMixin): "password_changed_at", "permissions", "state", + "preferred_timezone", } def __init__(self, _dict): super().__init__(_dict) self.permissions = _dict.get("permissions", {}) self._platform_admin = _dict["platform_admin"] + self.preferred_timezone = "US/Eastern" @classmethod def from_id(cls, user_id): diff --git a/app/statistics_utils.py b/app/statistics_utils.py index 953140d63..83c1eb154 100644 --- a/app/statistics_utils.py +++ b/app/statistics_utils.py @@ -1,8 +1,11 @@ from datetime import datetime from functools import reduce +import pytz from dateutil import parser +from app.utils.csv import get_user_preferred_timezone + def sum_of_statistics(delivery_statistics): statistics_keys = ( @@ -24,6 +27,7 @@ def sum_of_statistics(delivery_statistics): def add_rates_to(delivery_statistics): + et = pytz.timezone(get_user_preferred_timezone()) return dict( emails_failure_rate=get_formatted_percentage( delivery_statistics["emails_failed"], @@ -33,7 +37,7 @@ def add_rates_to(delivery_statistics): delivery_statistics["sms_failed"], delivery_statistics["sms_requested"] ), week_end_datetime=parser.parse( - delivery_statistics.get("week_end", str(datetime.utcnow())) + delivery_statistics.get("week_end", str(datetime.now(et))) ), **delivery_statistics ) diff --git a/app/utils/csv.py b/app/utils/csv.py index 37cc94238..54a18c2d7 100644 --- a/app/utils/csv.py +++ b/app/utils/csv.py @@ -1,3 +1,6 @@ +import datetime + +import pytz from flask import current_app from notifications_utils.recipients import RecipientCSV @@ -59,6 +62,10 @@ def get_errors_for_csv(recipients, template_type): return errors +def get_user_preferred_timezone(): + return "US/Eastern" + + def generate_notifications_csv(**kwargs): from app import notification_api_client from app.s3_client.s3_csv_client import s3download @@ -108,6 +115,10 @@ def generate_notifications_csv(**kwargs): **kwargs ) for notification in notifications_resp["notifications"]: + preferred_tz_created_at = convert_report_date_to_preferred_timezone( + notification["created_at"] + ) + current_app.logger.info(f"\n\n{notification}") if kwargs.get("job_id"): values = ( @@ -126,7 +137,7 @@ def generate_notifications_csv(**kwargs): notification["carrier"], notification["provider_response"], notification["status"], - notification["created_at"], + preferred_tz_created_at, ] ) else: @@ -139,7 +150,7 @@ def generate_notifications_csv(**kwargs): notification["carrier"], notification["provider_response"], notification["status"], - notification["created_at"], + preferred_tz_created_at, ] yield Spreadsheet.from_rows([map(str, values)]).as_csv_data @@ -148,3 +159,16 @@ def generate_notifications_csv(**kwargs): else: return raise Exception("Should never reach here") + + +def convert_report_date_to_preferred_timezone(db_date_str_in_utc): + date_arr = db_date_str_in_utc.split(" ") + db_date_str_in_utc = f"{date_arr[0]}T{date_arr[1]}+00:00" + utc_date_obj = datetime.datetime.fromisoformat(db_date_str_in_utc) + + utc_date_obj = utc_date_obj.astimezone(pytz.utc) + preferred_timezone = pytz.timezone(get_user_preferred_timezone()) + preferred_date_obj = utc_date_obj.astimezone(preferred_timezone) + preferred_tz_created_at = preferred_date_obj.strftime("%Y-%m-%d %H:%M:%S") + + return f"{preferred_tz_created_at} {get_user_preferred_timezone()}" diff --git a/app/utils/time.py b/app/utils/time.py index 4351b10aa..f8d874c96 100644 --- a/app/utils/time.py +++ b/app/utils/time.py @@ -3,9 +3,12 @@ from datetime import datetime import pytz from dateutil import parser +from app.utils.csv import get_user_preferred_timezone + def get_current_financial_year(): - now = datetime.now(pytz.utc) + et = pytz.timezone(get_user_preferred_timezone()) + now = datetime.now(et) current_month = int(now.strftime("%-m")) current_year = int(now.strftime("%Y")) return current_year if current_month > 9 else current_year - 1 diff --git a/tests/app/main/forms/test_choose_time_form.py b/tests/app/main/forms/test_choose_time_form.py index f3d402e8f..e87c96744 100644 --- a/tests/app/main/forms/test_choose_time_form.py +++ b/tests/app/main/forms/test_choose_time_form.py @@ -10,25 +10,25 @@ def test_form_contains_next_24h(notify_admin): # Friday assert choices[0] == ("", "Now") - assert choices[1] == ("2016-01-01T12:00:00", "Today at noon UTC") - assert choices[13] == ("2016-01-02T00:00:00", "Today at midnight UTC") + assert choices[1] == ("2016-01-01T07:00:00", "Today at 7am US/Eastern") + assert choices[13] == ("2016-01-01T19:00:00", "Today at 7pm US/Eastern") # Saturday - assert choices[14] == ("2016-01-02T01:00:00", "Tomorrow at 1am UTC") - assert choices[37] == ("2016-01-03T00:00:00", "Tomorrow at midnight UTC") + assert choices[14] == ("2016-01-01T20:00:00", "Today at 8pm US/Eastern") + assert choices[37] == ("2016-01-02T19:00:00", "Tomorrow at 7pm US/Eastern") # Sunday - assert choices[38] == ("2016-01-03T01:00:00", "Sunday at 1am UTC") + assert choices[38] == ("2016-01-02T20:00:00", "Tomorrow at 8pm US/Eastern") # Monday - assert choices[62] == ("2016-01-04T01:00:00", "Monday at 1am UTC") - assert choices[80] == ("2016-01-04T19:00:00", "Monday at 7pm UTC") - assert choices[84] == ("2016-01-04T23:00:00", "Monday at 11pm UTC") - assert choices[85] == ("2016-01-05T00:00:00", "Monday at midnight UTC") + assert choices[62] == ("2016-01-03T20:00:00", "Sunday at 8pm US/Eastern") + assert choices[80] == ("2016-01-04T14:00:00", "Monday at 2pm US/Eastern") + assert choices[84] == ("2016-01-04T18:00:00", "Monday at 6pm US/Eastern") + assert choices[85] == ("2016-01-04T19:00:00", "Monday at 7pm US/Eastern") with pytest.raises(IndexError): assert choices[ - 12 + (3 * 24) + 2 # hours left in the day # 3 days # magic number + 17 + (3 * 24) + 2 # hours left in the day # 3 days # magic number ] diff --git a/tests/app/main/test_formatters.py b/tests/app/main/test_formatters.py index e388db654..46e76da0d 100644 --- a/tests/app/main/test_formatters.py +++ b/tests/app/main/test_formatters.py @@ -87,38 +87,38 @@ def test_format_number_in_pounds_as_currency(input_number, formatted_number): ("time", "human_readable_datetime"), [ # incoming in UTC, outgoing in "human formatted" UTC - ("2018-03-14 09:00", "14 March at 09:00 UTC"), - ("2018-03-14 19:00", "14 March at 19:00 UTC"), - ("2018-03-15 09:00", "15 March at 09:00 UTC"), - ("2018-03-15 19:00", "15 March at 19:00 UTC"), - ("2018-03-19 09:00", "19 March at 09:00 UTC"), - ("2018-03-19 19:00", "19 March at 19:00 UTC"), - ("2018-03-19 23:59", "19 March at 23:59 UTC"), + ("2018-03-14 09:00", "14 March at 05:00 US/Eastern"), + ("2018-03-14 19:00", "14 March at 15:00 US/Eastern"), + ("2018-03-15 09:00", "15 March at 05:00 US/Eastern"), + ("2018-03-15 19:00", "15 March at 15:00 US/Eastern"), + ("2018-03-19 09:00", "19 March at 05:00 US/Eastern"), + ("2018-03-19 19:00", "19 March at 15:00 US/Eastern"), + ("2018-03-19 23:59", "19 March at 19:59 US/Eastern"), ( - "2018-03-20 00:00", - "19 March at 00:00 UTC", + "2018-03-20 04:00", + "19 March at 00:00 US/Eastern", ), # we specifically refer to 00:00 as belonging to the day before. - ("2018-03-20 04:01", "yesterday at 04:01 UTC"), - ("2018-03-20 09:00", "yesterday at 09:00 UTC"), - ("2018-03-20 19:00", "yesterday at 19:00 UTC"), - ("2018-03-20 23:59", "yesterday at 23:59 UTC"), + ("2018-03-20 04:01", "yesterday at 00:01 US/Eastern"), + ("2018-03-20 09:00", "yesterday at 05:00 US/Eastern"), + ("2018-03-20 19:00", "yesterday at 15:00 US/Eastern"), + ("2018-03-20 23:59", "yesterday at 19:59 US/Eastern"), ( - "2018-03-21 00:00", - "yesterday at 00:00 UTC", + "2018-03-21 04:00", + "yesterday at 00:00 US/Eastern", ), # we specifically refer to 00:00 as belonging to the day before. - ("2018-03-21 04:01", "today at 04:01 UTC"), - ("2018-03-21 09:00", "today at 09:00 UTC"), - ("2018-03-21 12:00", "today at 12:00 UTC"), - ("2018-03-21 19:00", "today at 19:00 UTC"), - ("2018-03-21 23:59", "today at 23:59 UTC"), - ("2018-03-22 00:00", "today at 00:00 UTC"), - ("2018-03-22 04:01", "tomorrow at 04:01 UTC"), - ("2018-03-22 09:00", "tomorrow at 09:00 UTC"), - ("2018-03-22 19:00", "tomorrow at 19:00 UTC"), - ("2018-03-22 23:59", "tomorrow at 23:59 UTC"), - ("2018-03-23 04:01", "23 March at 04:01 UTC"), - ("2018-03-23 09:00", "23 March at 09:00 UTC"), - ("2018-03-23 19:00", "23 March at 19:00 UTC"), + ("2018-03-21 04:01", "today at 00:01 US/Eastern"), + ("2018-03-21 09:00", "today at 05:00 US/Eastern"), + ("2018-03-21 12:00", "today at 08:00 US/Eastern"), + ("2018-03-21 19:00", "today at 15:00 US/Eastern"), + ("2018-03-21 23:59", "today at 19:59 US/Eastern"), + ("2018-03-22 00:00", "today at 20:00 US/Eastern"), + ("2018-03-22 04:01", "tomorrow at 00:01 US/Eastern"), + ("2018-03-22 09:00", "tomorrow at 05:00 US/Eastern"), + ("2018-03-22 19:00", "tomorrow at 15:00 US/Eastern"), + ("2018-03-22 23:59", "tomorrow at 19:59 US/Eastern"), + ("2018-03-23 04:01", "23 March at 00:01 US/Eastern"), + ("2018-03-23 09:00", "23 March at 05:00 US/Eastern"), + ("2018-03-23 19:00", "23 March at 15:00 US/Eastern"), ], ) def test_format_datetime_relative(time, human_readable_datetime): diff --git a/tests/app/main/views/test_activity.py b/tests/app/main/views/test_activity.py index ae3d44fdf..69e704827 100644 --- a/tests/app/main/views/test_activity.py +++ b/tests/app/main/views/test_activity.py @@ -159,7 +159,10 @@ def test_can_show_notifications( assert normalize_spaces( first_row.select_one(".table-field-right-aligned .align-with-message-body").text - ) == ("Delivered 1 January at 06:01 UTC") + ) in [ + "Delivered 1 January at 02:01 US/Eastern", + "Delivered 1 January at 01:01 US/Eastern", + ] assert page_title in page.h1.text.strip() @@ -653,31 +656,36 @@ def test_redacts_templates_that_should_be_redacted( @pytest.mark.parametrize( ("message_type", "status", "expected_hint_status", "single_line"), [ - ("email", "created", "Sending since 27 September at 12:30 UTC", True), - ("email", "sending", "Sending since 27 September at 12:30 UTC", True), + ("email", "created", "Sending since 27 September at 08:30 US/Eastern", True), + ("email", "sending", "Sending since 27 September at 08:30 US/Eastern", True), ( "email", "temporary-failure", - "Inbox not accepting messages right now 27 September at 12:31 UTC", + "Inbox not accepting messages right now 27 September at 08:31 US/Eastern", False, ), ( "email", "permanent-failure", - "Email address does not exist 27 September at 12:31 UTC", + "Email address does not exist 27 September at 08:31 US/Eastern", False, ), - ("email", "delivered", "Delivered 27 September at 12:31 UTC", True), - ("sms", "created", "Sending since 27 September at 12:30 UTC", True), - ("sms", "sending", "Sending since 27 September at 12:30 UTC", True), + ("email", "delivered", "Delivered 27 September at 08:31 US/Eastern", True), + ("sms", "created", "Sending since 27 September at 08:30 US/Eastern", True), + ("sms", "sending", "Sending since 27 September at 08:30 US/Eastern", True), ( "sms", "temporary-failure", - "Phone not accepting messages right now 27 September at 12:31 UTC", + "Phone not accepting messages right now 27 September at 08:31 US/Eastern", False, ), - ("sms", "permanent-failure", "Not delivered 27 September at 12:31 UTC", False), - ("sms", "delivered", "Delivered 27 September at 12:31 UTC", True), + ( + "sms", + "permanent-failure", + "Not delivered 27 September at 08:31 US/Eastern", + False, + ), + ("sms", "delivered", "Delivered 27 September at 08:31 US/Eastern", True), ], ) def test_sending_status_hint_displays_correctly_on_notifications_page( diff --git a/tests/app/main/views/test_conversation.py b/tests/app/main/views/test_conversation.py index 3e8459cd7..215a8e828 100644 --- a/tests/app/main/views/test_conversation.py +++ b/tests/app/main/views/test_conversation.py @@ -70,7 +70,7 @@ def test_get_user_phone_number_raises_if_both_api_requests_fail(mocker): (False, "Hello Jo"), ], ) -@freeze_time("2012-01-01 00:00:00") +@freeze_time("2012-01-01 05:00:00") def test_view_conversation( client_request, mocker, @@ -113,55 +113,55 @@ def test_view_conversation( [ ( "message-8", - "yesterday at 14:59 UTC", + "yesterday at 14:59 US/Eastern", ), ( "message-7", - "yesterday at 14:59 UTC", + "yesterday at 14:59 US/Eastern", ), ( "message-6", - "yesterday at 16:59 UTC", + "yesterday at 16:59 US/Eastern", ), ( "message-5", - "yesterday at 18:59 UTC", + "yesterday at 18:59 US/Eastern", ), ( "message-4", - "yesterday at 20:59 UTC", + "yesterday at 20:59 US/Eastern", ), ( "message-3", - "yesterday at 22:59 UTC", + "yesterday at 22:59 US/Eastern", ), ( "message-2", - "yesterday at 22:59 UTC", + "yesterday at 22:59 US/Eastern", ), ( "message-1", - "yesterday at 23:00 UTC", + "yesterday at 23:00 US/Eastern", ), ( expected_outbound_content, - "yesterday at 00:00 UTC", + "yesterday at 00:00 US/Eastern", ), ( expected_outbound_content, - "yesterday at 00:00 UTC", + "yesterday at 00:00 US/Eastern", ), ( expected_outbound_content, - "yesterday at 00:00 UTC", + "yesterday at 00:00 US/Eastern", ), ( expected_outbound_content, - "yesterday at 00:00 UTC", + "yesterday at 00:00 US/Eastern", ), ( expected_outbound_content, - "yesterday at 00:00 UTC", + "yesterday at 00:00 US/Eastern", ), ] ): diff --git a/tests/app/main/views/test_dashboard.py b/tests/app/main/views/test_dashboard.py index f0e07f105..349a35d7f 100644 --- a/tests/app/main/views/test_dashboard.py +++ b/tests/app/main/views/test_dashboard.py @@ -379,7 +379,7 @@ def test_view_inbox_updates( mock_get_partials.assert_called_once_with(SERVICE_ONE_ID) -@freeze_time("2016-07-01 12:00") +@freeze_time("2016-07-01 16:00") def test_download_inbox( client_request, mock_get_inbound_sms, @@ -394,14 +394,14 @@ def test_download_inbox( ) assert response.get_data(as_text=True) == ( "Phone number,Message,Received\r\n" - "(202) 867-5300,message-1,2016-07-01 11:00 UTC\r\n" - "(202) 867-5300,message-2,2016-07-01 10:59 UTC\r\n" - "(202) 867-5300,message-3,2016-07-01 10:59 UTC\r\n" - "(202) 867-5302,message-4,2016-07-01 08:59 UTC\r\n" - "+33 1 12 34 56 78,message-5,2016-07-01 06:59 UTC\r\n" - "(202) 555-0104,message-6,2016-07-01 04:59 UTC\r\n" - "(202) 555-0104,message-7,2016-07-01 02:59 UTC\r\n" - "+682 12345,message-8,2016-07-01 02:59 UTC\r\n" + "(202) 867-5300,message-1,2016-07-01 11:00 US/Eastern\r\n" + "(202) 867-5300,message-2,2016-07-01 10:59 US/Eastern\r\n" + "(202) 867-5300,message-3,2016-07-01 10:59 US/Eastern\r\n" + "(202) 867-5302,message-4,2016-07-01 08:59 US/Eastern\r\n" + "+33 1 12 34 56 78,message-5,2016-07-01 06:59 US/Eastern\r\n" + "(202) 555-0104,message-6,2016-07-01 04:59 US/Eastern\r\n" + "(202) 555-0104,message-7,2016-07-01 02:59 US/Eastern\r\n" + "+682 12345,message-8,2016-07-01 02:59 US/Eastern\r\n" ) @@ -636,7 +636,7 @@ def test_monthly_has_equal_length_tables( assert page.select_one(".table-field-headings th").get("width") == "33%" -@freeze_time("2016-01-01 1:09:00.061258") +@freeze_time("2016-01-01 11:09:00.061258") def test_should_show_upcoming_jobs_on_dashboard( client_request, mock_get_service_templates, @@ -659,7 +659,7 @@ def test_should_show_upcoming_jobs_on_dashboard( assert normalize_spaces(page.select_one("main h2").text) == ("In the next few days") assert normalize_spaces(page.select_one("a.banner-dashboard").text) == ( - "2 files waiting to send " "- sending starts today at 11:09 UTC" + "2 files waiting to send " "- sending starts today at 06:09 US/Eastern" ) assert page.select_one("a.banner-dashboard")["href"] == url_for( diff --git a/tests/app/main/views/test_history.py b/tests/app/main/views/test_history.py index 184e0b061..7f4e482b4 100644 --- a/tests/app/main/views/test_history.py +++ b/tests/app/main/views/test_history.py @@ -11,30 +11,30 @@ from tests.conftest import SERVICE_ONE_ID, normalize_spaces {}, [ ( - "12 December", + "12 December 2012", ( - "Test User 18:13 " + "Test User 13:13 " "Renamed this service from ‘Before lunch’ to ‘After lunch’ " - "Test User 17:12 " + "Test User 12:12 " "Renamed this service from ‘Example service’ to ‘Before lunch’" ), ), ( - "11 November", - ("Test User 17:12 " "Revoked the ‘Bad key’ API key"), + "11 November 2012", + ("Test User 12:12 " "Revoked the ‘Bad key’ API key"), ), ( - "11 November 2011", - ("Test User 16:11 " "Created an API key called ‘Bad key’"), + "11 November", + ("Test User 11:11 " "Created an API key called ‘Bad key’"), ), ( "10 October 2010", ( - "Test User 15:10 " + "Test User 11:10 " "Created an API key called ‘Good key’ " - "Test User 14:09 " + "Test User 10:09 " "Created an API key called ‘Key event returned in non-chronological order’ " - "Test User 06:01 " + "Test User 02:01 " "Created this service and called it ‘Example service’" ), ), @@ -44,19 +44,19 @@ from tests.conftest import SERVICE_ONE_ID, normalize_spaces {"selected": "api"}, [ ( - "11 November", - ("Test User 17:12 " "Revoked the ‘Bad key’ API key"), + "11 November 2012", + ("Test User 12:12 " "Revoked the ‘Bad key’ API key"), ), ( - "11 November 2011", - ("Test User 16:11 " "Created an API key called ‘Bad key’"), + "11 November", + ("Test User 11:11 " "Created an API key called ‘Bad key’"), ), ( "10 October 2010", ( - "Test User 15:10 " + "Test User 11:10 " "Created an API key called ‘Good key’ " - "Test User 14:09 " + "Test User 10:09 " "Created an API key called ‘Key event returned in non-chronological order’" ), ), @@ -66,18 +66,18 @@ from tests.conftest import SERVICE_ONE_ID, normalize_spaces {"selected": "service"}, [ ( - "12 December", + "12 December 2012", ( - "Test User 18:13 " + "Test User 13:13 " "Renamed this service from ‘Before lunch’ to ‘After lunch’ " - "Test User 17:12 " + "Test User 12:12 " "Renamed this service from ‘Example service’ to ‘Before lunch’" ), ), ( "10 October 2010", ( - "Test User 06:01 " + "Test User 02:01 " "Created this service and called it ‘Example service’" ), ), diff --git a/tests/app/main/views/test_jobs.py b/tests/app/main/views/test_jobs.py index 862fd17e5..cc611f32b 100644 --- a/tests/app/main/views/test_jobs.py +++ b/tests/app/main/views/test_jobs.py @@ -90,7 +90,7 @@ def test_should_show_page_for_one_job( assert page.h1.text.strip() == "thisisatest.csv" assert " ".join(page.find("tbody").find("tr").text.split()) == ( - "2021234567 template content Delivered 1 January at 11:10 UTC" + "2021234567 template content Delivered 1 January at 06:10 US/Eastern" ) assert page.find("div", {"data-key": "notifications"})["data-resource"] == url_for( "main.view_job_updates", @@ -109,7 +109,7 @@ def test_should_show_page_for_one_job( assert page.find("span", {"id": "time-left"}).text == "Data available for 7 days" assert normalize_spaces(page.select_one("tbody tr").text) == normalize_spaces( - "2021234567 " "template content " "Delivered 1 January at 11:10 UTC" + "2021234567 " "template content " "Delivered 1 January at 06:10 US/Eastern" ) assert page.select_one("tbody tr a")["href"] == url_for( "main.view_notification", @@ -334,7 +334,7 @@ def test_should_show_old_job( ] -@freeze_time("2016-01-01T05:00:00.061258") +@freeze_time("2016-01-01T06:00:00.061258") def test_should_show_scheduled_job( client_request, mock_get_service_template, @@ -350,7 +350,7 @@ def test_should_show_scheduled_job( ) assert normalize_spaces(page.select("main p")[1].text) == ( - "Sending Two week reminder tomorrow at 05:00 UTC" + "Sending Two week reminder today at 00:00 US/Eastern" ) assert page.select("main p a")[0]["href"] == url_for( "main.view_template_version", @@ -423,8 +423,8 @@ def test_should_show_updates_for_one_job_as_json( assert "2021234567" in content["notifications"] assert "Status" in content["notifications"] assert "Delivered" in content["notifications"] - assert "05:01" in content["notifications"] - assert "Sent by Test User on 1 January at 05:00" in content["status"] + assert "00:01" in content["notifications"] + assert "Sent by Test User on 1 January at 00:00" in content["status"] @freeze_time("2016-01-01 05:00:00.000001") @@ -465,8 +465,8 @@ def test_should_show_updates_for_scheduled_job_as_json( assert "2021234567" in content["notifications"] assert "Status" in content["notifications"] assert "Delivered" in content["notifications"] - assert "05:01" in content["notifications"] - assert "Sent by Test User on 1 June at 20:00" in content["status"] + assert "00:01" in content["notifications"] + assert "Sent by Test User on 1 June at 16:00" in content["status"] @pytest.mark.parametrize( diff --git a/tests/app/main/views/test_notifications.py b/tests/app/main/views/test_notifications.py index 3f522ae83..7020cd3e0 100644 --- a/tests/app/main/views/test_notifications.py +++ b/tests/app/main/views/test_notifications.py @@ -226,19 +226,23 @@ def test_notification_status_shows_expected_back_link( [ ( "2012-01-01 06:01", - ("‘sample template’ was sent by Test User today at 06:01 UTC"), + ("‘sample template’ was sent by Test User today at 01:01 US/Eastern"), ), ( "2012-01-02 06:01", - ("‘sample template’ was sent by Test User yesterday at 06:01 UTC"), + ("‘sample template’ was sent by Test User yesterday at 01:01 US/Eastern"), ), ( "2012-01-03 06:01", - ("‘sample template’ was sent by Test User on 1 January at 06:01 UTC"), + ( + "‘sample template’ was sent by Test User on 1 January at 01:01 US/Eastern" + ), ), ( "2013-01-03 06:01", - ("‘sample template’ was sent by Test User on 1 January 2012 at 06:01 UTC"), + ( + "‘sample template’ was sent by Test User on 1 January 2012 at 01:01 US/Eastern" + ), ), ], ) diff --git a/tests/app/main/views/test_providers.py b/tests/app/main/views/test_providers.py index d3a79599f..776868cd6 100644 --- a/tests/app/main/views/test_providers.py +++ b/tests/app/main/views/test_providers.py @@ -200,7 +200,7 @@ def test_view_providers_shows_all_providers( assert table_data[1].text.strip() == "20" assert table_data[2].text.strip() == "42" assert table_data[3].text.strip() == "True" - assert table_data[4].text.strip() == "16 January at 15:20 UTC" + assert table_data[4].text.strip() == "16 January at 10:20 US/Eastern" assert table_data[5].text.strip() == "Test User" domestic_sms_second_row = domestic_sms_table.tbody.find_all("tr")[1] diff --git a/tests/app/main/views/test_templates.py b/tests/app/main/views/test_templates.py index b5ce171b4..db33dfa28 100644 --- a/tests/app/main/views/test_templates.py +++ b/tests/app/main/views/test_templates.py @@ -472,7 +472,7 @@ def test_caseworker_redirected_to_set_sender_for_one_off( ) -@freeze_time("2020-01-01 15:00") +@freeze_time("2020-01-01 20:00") def test_caseworker_sees_template_page_if_template_is_deleted( client_request, mock_get_deleted_template, @@ -497,7 +497,7 @@ def test_caseworker_sees_template_page_if_template_is_deleted( ) assert ( page.select("p.hint")[0].text.strip() - == "This template was deleted today at 15:00 UTC." + == "This template was deleted today at 15:00 US/Eastern." ) mock_get_deleted_template.assert_called_with(SERVICE_ONE_ID, template_id, None) @@ -1573,7 +1573,7 @@ def test_should_redirect_when_deleting_a_template( mock_delete_service_template.assert_called_with(SERVICE_ONE_ID, TEMPLATE_ONE_ID) -@freeze_time("2016-01-01T15:00") +@freeze_time("2016-01-01T20:00") def test_should_show_page_for_a_deleted_template( client_request, mock_get_template_folders, @@ -1606,7 +1606,7 @@ def test_should_show_page_for_a_deleted_template( ) assert ( page.select("p.hint")[0].text.strip() - == "This template was deleted today at 15:00 UTC." + == "This template was deleted today at 15:00 US/Eastern." ) assert "Delete this template" not in page.select_one("main").text diff --git a/tests/app/main/views/uploads/test_upload_hub.py b/tests/app/main/views/uploads/test_upload_hub.py index 2c7140c6c..ee24fba65 100644 --- a/tests/app/main/views/uploads/test_upload_hub.py +++ b/tests/app/main/views/uploads/test_upload_hub.py @@ -51,7 +51,9 @@ def test_get_upload_hub_page( assert len(uploads) == 1 assert normalize_spaces(uploads[0].text.strip()) == ( - "some.csv " "Sent 1 January 2016 at 11:09 UTC " "0 pending 8 delivered 2 failed" + "some.csv " + "Sent 1 January 2016 at 06:09 US/Eastern " + "0 pending 8 delivered 2 failed" ) assert uploads[0].select_one("a.file-list-filename-large")["href"] == ( "/services/{}/jobs/job_id_1".format(SERVICE_ONE_ID) @@ -80,12 +82,12 @@ def test_uploads_page_shows_scheduled_jobs( ("File Status"), ( "even_later.csv " - "Sending 1 January 2016 at 23:09 UTC " + "Sending 1 January 2016 at 18:09 US/Eastern " "1 text message waiting to send" ), ( "send_me_later.csv " - "Sending 1 January 2016 at 11:09 UTC " + "Sending 1 January 2016 at 06:09 US/Eastern " "1 text message waiting to send" ), ] diff --git a/tests/app/utils/test_csv.py b/tests/app/utils/test_csv.py index f56fd2548..b4800a699 100644 --- a/tests/app/utils/test_csv.py +++ b/tests/app/utils/test_csv.py @@ -4,7 +4,11 @@ from io import StringIO import pytest -from app.utils.csv import generate_notifications_csv, get_errors_for_csv +from app.utils.csv import ( + convert_report_date_to_preferred_timezone, + generate_notifications_csv, + get_errors_for_csv, +) from tests.conftest import fake_uuid @@ -87,14 +91,14 @@ def get_notifications_csv_mock( None, [ "Recipient,Template,Type,Sent by,Job,Carrier,Carrier Response,Status,Time\n", - "foo@bar.com,foo,sms,,,ATT Mobility,Did not like it,Delivered,1943-04-19 12:00:00\r\n", + "foo@bar.com,foo,sms,,,ATT Mobility,Did not like it,Delivered,1943-04-19 08:00:00 US/Eastern\r\n", ], ), ( "Anne Example", [ "Recipient,Template,Type,Sent by,Job,Carrier,Carrier Response,Status,Time\n", - "foo@bar.com,foo,sms,Anne Example,,ATT Mobility,Did not like it,Delivered,1943-04-19 12:00:00\r\n", + "foo@bar.com,foo,sms,Anne Example,,ATT Mobility,Did not like it,Delivered,1943-04-19 08:00:00 US/Eastern\r\n", # noqa ], ), ], @@ -147,7 +151,7 @@ def test_generate_notifications_csv_without_job( "ATT Mobility", "Did not like it", "Delivered", - "1943-04-19 12:00:00", + "1943-04-19 08:00:00 US/Eastern", ], ), ( @@ -183,7 +187,7 @@ def test_generate_notifications_csv_without_job( "ATT Mobility", "Did not like it", "Delivered", - "1943-04-19 12:00:00", + "1943-04-19 08:00:00 US/Eastern", ], ), ( @@ -219,7 +223,7 @@ def test_generate_notifications_csv_without_job( "ATT Mobility", "Did not like it", "Delivered", - "1943-04-19 12:00:00", + "1943-04-19 08:00:00 US/Eastern", ], ), ], @@ -389,3 +393,9 @@ def test_get_errors_for_csv( ) == expected_errors ) + + +def test_convert_report_date_to_preferred_timezone(): + original = "2023-11-16 05:00:00" + altered = convert_report_date_to_preferred_timezone(original) + assert altered == "2023-11-16 00:00:00 US/Eastern"