diff --git a/tests/test_invite.py b/tests/test_invite.py
index 295103208ecd1f1436e4066e7fadd5fe55c2f226..93dea192ec59f0fe15236cda36106f0e6ebe5bd9 100644
--- a/tests/test_invite.py
+++ b/tests/test_invite.py
@@ -12,21 +12,21 @@ from utils import dump, UffdTestCase, db_flush
 
 class TestInviteModel(UffdTestCase):
 	def test_expire(self):
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), creator=self.get_admin())
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), creator=self.get_admin())
 		self.assertFalse(invite.expired)
 		self.assertTrue(invite.active)
-		invite.valid_until = datetime.datetime.now() - datetime.timedelta(seconds=60)
+		invite.valid_until = datetime.datetime.utcnow() - datetime.timedelta(seconds=60)
 		self.assertTrue(invite.expired)
 		self.assertFalse(invite.active)
 
 	def test_void(self):
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), single_use=False, creator=self.get_admin())
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), single_use=False, creator=self.get_admin())
 		self.assertFalse(invite.voided)
 		self.assertTrue(invite.active)
 		invite.used = True
 		self.assertFalse(invite.voided)
 		self.assertTrue(invite.active)
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), single_use=True, creator=self.get_admin())
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), single_use=True, creator=self.get_admin())
 		self.assertFalse(invite.voided)
 		self.assertTrue(invite.active)
 		invite.used = True
@@ -35,7 +35,7 @@ class TestInviteModel(UffdTestCase):
 
 	def test_permitted(self):
 		role = Role(name='testrole')
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True, roles=[role])
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, roles=[role])
 		self.assertFalse(invite.permitted)
 		self.assertFalse(invite.active)
 		invite.creator = self.get_admin()
@@ -57,26 +57,26 @@ class TestInviteModel(UffdTestCase):
 		self.assertFalse(invite.active)
 
 	def test_disable(self):
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), creator=self.get_admin())
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), creator=self.get_admin())
 		self.assertTrue(invite.active)
 		invite.disable()
 		self.assertFalse(invite.active)
 
 	def test_reset_disabled(self):
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), creator=self.get_admin())
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), creator=self.get_admin())
 		invite.disable()
 		self.assertFalse(invite.active)
 		invite.reset()
 		self.assertTrue(invite.active)
 
 	def test_reset_expired(self):
-		invite = Invite(valid_until=datetime.datetime.now() - datetime.timedelta(seconds=60), creator=self.get_admin())
+		invite = Invite(valid_until=datetime.datetime.utcnow() - datetime.timedelta(seconds=60), creator=self.get_admin())
 		self.assertFalse(invite.active)
 		invite.reset()
 		self.assertFalse(invite.active)
 
 	def test_reset_single_use(self):
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), single_use=False, creator=self.get_admin())
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), single_use=False, creator=self.get_admin())
 		invite.used = True
 		invite.disable()
 		self.assertFalse(invite.active)
@@ -84,7 +84,7 @@ class TestInviteModel(UffdTestCase):
 		self.assertTrue(invite.active)
 
 	def test_short_token(self):
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), creator=self.get_admin())
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), creator=self.get_admin())
 		db.session.add(invite)
 		db.session.commit()
 		self.assertTrue(len(invite.short_token) <= len(invite.token)/3)
@@ -102,7 +102,7 @@ class TestInviteGrantModel(UffdTestCase):
 		db.session.add(role1)
 		role2 = Role(name='testrole2')
 		db.session.add(role2)
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), roles=[role1, role2], creator=self.get_admin())
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), roles=[role1, role2], creator=self.get_admin())
 		self.assertIn(role0, user.roles)
 		self.assertNotIn(role1, user.roles)
 		self.assertNotIn(role2, user.roles)
@@ -132,7 +132,7 @@ class TestInviteGrantModel(UffdTestCase):
 		group = self.get_admin_group()
 		role = Role(name='testrole1', groups={group: RoleGroup(group=group)})
 		db.session.add(role)
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), roles=[role], single_use=True, used=True, creator=self.get_admin())
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), roles=[role], single_use=True, used=True, creator=self.get_admin())
 		self.assertFalse(invite.active)
 		grant = InviteGrant(invite=invite, user=user)
 		success, msg = grant.apply()
@@ -143,7 +143,7 @@ class TestInviteGrantModel(UffdTestCase):
 
 	def test_no_roles(self):
 		user = self.get_user()
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), creator=self.get_admin())
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), creator=self.get_admin())
 		self.assertTrue(invite.active)
 		grant = InviteGrant(invite=invite, user=user)
 		success, msg = grant.apply()
@@ -155,7 +155,7 @@ class TestInviteGrantModel(UffdTestCase):
 		role = Role(name='testrole1')
 		db.session.add(role)
 		user.roles.append(role)
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), roles=[role], creator=self.get_admin())
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), roles=[role], creator=self.get_admin())
 		self.assertTrue(invite.active)
 		grant = InviteGrant(invite=invite, user=user)
 		success, msg = grant.apply()
@@ -180,7 +180,7 @@ class TestInviteSignupModel(UffdTestCase):
 		db.session.add(role1)
 		role2 = Role(name='testrole2')
 		db.session.add(role2)
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), roles=[role1, role2], allow_signup=True, creator=self.get_admin())
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), roles=[role1, role2], allow_signup=True, creator=self.get_admin())
 		signup = InviteSignup(invite=invite, loginname='newuser', displayname='New User', mail='test@example.com', password='notsecret')
 		self.assertFalse(invite.used)
 		valid, msg = signup.validate()
@@ -208,7 +208,7 @@ class TestInviteSignupModel(UffdTestCase):
 		base_role = Role.query.filter_by(name='base').one()
 		base_group1 = self.get_access_group()
 		base_group2 = self.get_users_group()
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
 		signup = InviteSignup(invite=invite, loginname='newuser', displayname='New User', mail='test@example.com', password='notsecret')
 		self.assertFalse(invite.used)
 		valid, msg = signup.validate()
@@ -231,7 +231,7 @@ class TestInviteSignupModel(UffdTestCase):
 		self.assertEqual(len(User.query.filter_by(loginname='newuser').all()), 1)
 
 	def test_inactive(self):
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True, single_use=True, used=True, creator=self.get_admin())
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, single_use=True, used=True, creator=self.get_admin())
 		self.assertFalse(invite.active)
 		signup = InviteSignup(invite=invite, loginname='newuser', displayname='New User', mail='test@example.com', password='notsecret')
 		valid, msg = signup.validate()
@@ -242,7 +242,7 @@ class TestInviteSignupModel(UffdTestCase):
 		self.assertIsInstance(msg, str)
 
 	def test_invalid(self):
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
 		self.assertTrue(invite.active)
 		signup = InviteSignup(invite=invite, loginname='', displayname='New User', mail='test@example.com', password='notsecret')
 		valid, msg = signup.validate()
@@ -250,7 +250,7 @@ class TestInviteSignupModel(UffdTestCase):
 		self.assertIsInstance(msg, str)
 
 	def test_invalid2(self):
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
 		self.assertTrue(invite.active)
 		signup = InviteSignup(invite=invite, loginname='newuser', displayname='New User', mail='test@example.com', password='notsecret')
 		user, msg = signup.finish('wrongpassword')
@@ -258,7 +258,7 @@ class TestInviteSignupModel(UffdTestCase):
 		self.assertIsInstance(msg, str)
 
 	def test_no_signup(self):
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=False, creator=self.get_admin())
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=False, creator=self.get_admin())
 		self.assertTrue(invite.active)
 		signup = InviteSignup(invite=invite, loginname='newuser', displayname='New User', mail='test@example.com', password='notsecret')
 		valid, msg = signup.validate()
@@ -274,8 +274,8 @@ class TestInviteAdminViews(UffdTestCase):
 		self.app.last_mail = None
 
 	def test_index(self):
-		valid_until = datetime.datetime.now() + datetime.timedelta(seconds=60)
-		valid_until_expired = datetime.datetime.now() - datetime.timedelta(seconds=60)
+		valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
+		valid_until_expired = datetime.datetime.utcnow() - datetime.timedelta(seconds=60)
 		user1 = self.get_user()
 		user2 = self.get_admin()
 		role1 = Role(name='testrole1')
@@ -320,7 +320,7 @@ class TestInviteAdminViews(UffdTestCase):
 
 	def test_index_signupperm(self):
 		current_app.config['ACL_SIGNUP_GROUP'] = 'uffd_access'
-		valid_until = datetime.datetime.now() + datetime.timedelta(seconds=60)
+		valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
 		invite1 = Invite(valid_until=valid_until, allow_signup=True, creator=self.get_admin())
 		db.session.add(invite1)
 		invite2 = Invite(valid_until=valid_until, allow_signup=True, creator=self.get_user())
@@ -343,7 +343,7 @@ class TestInviteAdminViews(UffdTestCase):
 		db.session.add(role1)
 		role2 = Role(name='testrole2', moderator_group=self.get_access_group())
 		db.session.add(role2)
-		valid_until = datetime.datetime.now() + datetime.timedelta(seconds=60)
+		valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
 		db.session.add(Invite(valid_until=valid_until, roles=[role1]))
 		db.session.add(Invite(valid_until=valid_until, roles=[role2]))
 		db.session.commit()
@@ -362,10 +362,10 @@ class TestInviteAdminViews(UffdTestCase):
 		r = self.client.get(path=url_for('invite.new'), follow_redirects=True)
 		dump('invite_new', r)
 		self.assertEqual(r.status_code, 200)
-		valid_until = (datetime.datetime.now() + datetime.timedelta(seconds=60)).isoformat(timespec='minutes')
+		valid_until_input = (datetime.datetime.now() + datetime.timedelta(seconds=60)).isoformat(timespec='minutes')
 		self.assertListEqual(Invite.query.all(), [])
 		r = self.client.post(path=url_for('invite.new_submit'),
-			data={'single-use': '1', 'valid-until': valid_until,
+			data={'single-use': '1', 'valid-until': valid_until_input,
 			      'allow-signup': '1', 'role-%d'%role_id: '1'}, follow_redirects=True)
 		dump('invite_new_submit', r)
 		invite = Invite.query.one()
@@ -382,9 +382,9 @@ class TestInviteAdminViews(UffdTestCase):
 		db.session.add(role)
 		db.session.commit()
 		role_id = role.id
-		valid_until = (datetime.datetime.now() + datetime.timedelta(seconds=60)).isoformat(timespec='minutes')
+		valid_until_input = (datetime.datetime.now() + datetime.timedelta(seconds=60)).isoformat(timespec='minutes')
 		r = self.client.post(path=url_for('invite.new_submit'),
-			data={'single-use': '1', 'valid-until': valid_until,
+			data={'single-use': '1', 'valid-until': valid_until_input,
 			      'allow-signup': '1', 'role-%d'%role_id: '1'}, follow_redirects=True)
 		dump('invite_new_noperm', r)
 		self.assertIsNone(Invite.query.first())
@@ -392,16 +392,16 @@ class TestInviteAdminViews(UffdTestCase):
 	def test_new_empty(self):
 		current_app.config['ACL_SIGNUP_GROUP'] = 'uffd_access'
 		self.login_as('user')
-		valid_until = (datetime.datetime.now() + datetime.timedelta(seconds=60)).isoformat(timespec='minutes')
+		valid_until_input = (datetime.datetime.now() + datetime.timedelta(seconds=60)).isoformat(timespec='minutes')
 		r = self.client.post(path=url_for('invite.new_submit'),
-			data={'single-use': '1', 'valid-until': valid_until,
+			data={'single-use': '1', 'valid-until': valid_until_input,
 			      'allow-signup': '0'}, follow_redirects=True)
 		dump('invite_new_empty', r)
 		self.assertIsNone(Invite.query.first())
 
 	def test_disable(self):
 		self.login_as('admin')
-		valid_until = datetime.datetime.now() + datetime.timedelta(seconds=60)
+		valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
 		invite = Invite(valid_until=valid_until, creator=self.get_admin())
 		db.session.add(invite)
 		db.session.commit()
@@ -414,7 +414,7 @@ class TestInviteAdminViews(UffdTestCase):
 	def test_disable_own(self):
 		current_app.config['ACL_SIGNUP_GROUP'] = 'uffd_access'
 		self.login_as('user')
-		valid_until = datetime.datetime.now() + datetime.timedelta(seconds=60)
+		valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
 		invite = Invite(valid_until=valid_until, creator=self.get_user())
 		db.session.add(invite)
 		db.session.commit()
@@ -426,7 +426,7 @@ class TestInviteAdminViews(UffdTestCase):
 
 	def test_disable_rolemod(self):
 		self.login_as('user')
-		valid_until = datetime.datetime.now() + datetime.timedelta(seconds=60)
+		valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
 		role = Role(name='testrole', moderator_group=self.get_access_group())
 		db.session.add(role)
 		invite = Invite(valid_until=valid_until, creator=self.get_admin(), roles=[role])
@@ -439,7 +439,7 @@ class TestInviteAdminViews(UffdTestCase):
 
 	def test_disable_noperm(self):
 		self.login_as('user')
-		valid_until = datetime.datetime.now() + datetime.timedelta(seconds=60)
+		valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
 		db.session.add(Role(name='testrole1', moderator_group=self.get_access_group()))
 		role = Role(name='testrole2', moderator_group=self.get_admin_group())
 		db.session.add(role)
@@ -453,7 +453,7 @@ class TestInviteAdminViews(UffdTestCase):
 
 	def test_reset_disabled(self):
 		self.login_as('admin')
-		valid_until = datetime.datetime.now() + datetime.timedelta(seconds=60)
+		valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
 		invite = Invite(valid_until=valid_until, disabled=True, creator=self.get_admin())
 		db.session.add(invite)
 		db.session.commit()
@@ -465,7 +465,7 @@ class TestInviteAdminViews(UffdTestCase):
 
 	def test_reset_voided(self):
 		self.login_as('admin')
-		valid_until = datetime.datetime.now() + datetime.timedelta(seconds=60)
+		valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
 		invite = Invite(valid_until=valid_until, single_use=True, used=True, creator=self.get_admin())
 		db.session.add(invite)
 		db.session.commit()
@@ -478,7 +478,7 @@ class TestInviteAdminViews(UffdTestCase):
 	def test_reset_own(self):
 		current_app.config['ACL_SIGNUP_GROUP'] = 'uffd_access'
 		self.login_as('user')
-		valid_until = datetime.datetime.now() + datetime.timedelta(seconds=60)
+		valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
 		invite = Invite(valid_until=valid_until, disabled=True, creator=self.get_user())
 		db.session.add(invite)
 		db.session.commit()
@@ -490,7 +490,7 @@ class TestInviteAdminViews(UffdTestCase):
 
 	def test_reset_foreign(self):
 		self.login_as('user')
-		valid_until = datetime.datetime.now() + datetime.timedelta(seconds=60)
+		valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
 		role = Role(name='testrole', moderator_group=self.get_access_group())
 		db.session.add(role)
 		invite = Invite(valid_until=valid_until, disabled=True, creator=self.get_admin(), roles=[role])
@@ -507,7 +507,7 @@ class TestInviteUseViews(UffdTestCase):
 		self.app.last_mail = None
 
 	def test_use(self):
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True, roles=[Role(name='testrole1'), Role(name='testrole2')])
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, roles=[Role(name='testrole1'), Role(name='testrole2')])
 		db.session.add(invite)
 		db.session.commit()
 		token = invite.token
@@ -517,7 +517,7 @@ class TestInviteUseViews(UffdTestCase):
 
 	def test_use_loggedin(self):
 		self.login_as('user')
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True, roles=[Role(name='testrole1'), Role(name='testrole2')])
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, roles=[Role(name='testrole1'), Role(name='testrole2')])
 		db.session.add(invite)
 		db.session.commit()
 		token = invite.token
@@ -526,7 +526,7 @@ class TestInviteUseViews(UffdTestCase):
 		self.assertEqual(r.status_code, 200)
 
 	def test_use_inactive(self):
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), disabled=True)
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), disabled=True)
 		db.session.add(invite)
 		db.session.commit()
 		token = invite.token
@@ -548,7 +548,7 @@ class TestInviteUseViews(UffdTestCase):
 		db.session.add(role1)
 		role2 = Role(name='testrole2')
 		db.session.add(role2)
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), roles=[role1, role2], creator=self.get_admin())
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), roles=[role1, role2], creator=self.get_admin())
 		db.session.add(invite)
 		db.session.commit()
 		invite_id = invite.id
@@ -574,7 +574,7 @@ class TestInviteUseViews(UffdTestCase):
 		self.assertIn(self.get_admin_group(), user.groups)
 
 	def test_grant_invalid_invite(self):
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), disabled=True)
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), disabled=True)
 		db.session.add(invite)
 		db.session.commit()
 		invite_id = invite.id
@@ -586,7 +586,7 @@ class TestInviteUseViews(UffdTestCase):
 		self.assertFalse(Invite.query.filter_by(token=token).first().used)
 
 	def test_grant_no_roles(self):
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60))
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60))
 		db.session.add(invite)
 		db.session.commit()
 		invite_id = invite.id
@@ -602,7 +602,7 @@ class TestInviteUseViews(UffdTestCase):
 		role = Role(name='testrole')
 		db.session.add(role)
 		user.roles.append(role)
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), roles=[role])
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), roles=[role])
 		db.session.add(invite)
 		db.session.commit()
 		invite_id = invite.id
@@ -623,7 +623,7 @@ class TestInviteUseViews(UffdTestCase):
 		db.session.add(role1)
 		role2 = Role(name='testrole2')
 		db.session.add(role2)
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), roles=[role1, role2], allow_signup=True, creator=self.get_admin())
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), roles=[role1, role2], allow_signup=True, creator=self.get_admin())
 		db.session.add(invite)
 		db.session.commit()
 		invite_id = invite.id
@@ -645,7 +645,7 @@ class TestInviteUseViews(UffdTestCase):
 		self.assertTrue(signup.validate()[0])
 
 	def test_signup_invalid_invite(self):
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True, disabled=True, creator=self.get_admin())
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, disabled=True, creator=self.get_admin())
 		db.session.add(invite)
 		db.session.commit()
 		r = self.client.get(path=url_for('invite.signup_start', invite_id=invite.id, token=invite.token), follow_redirects=True)
@@ -653,7 +653,7 @@ class TestInviteUseViews(UffdTestCase):
 		self.assertEqual(r.status_code, 200)
 
 	def test_signup_nosignup(self):
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=False, creator=self.get_admin())
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=False, creator=self.get_admin())
 		db.session.add(invite)
 		db.session.commit()
 		r = self.client.get(path=url_for('invite.signup_start', invite_id=invite.id, token=invite.token), follow_redirects=True)
@@ -661,7 +661,7 @@ class TestInviteUseViews(UffdTestCase):
 		self.assertEqual(r.status_code, 200)
 
 	def test_signup_wrongpassword(self):
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
 		db.session.add(invite)
 		db.session.commit()
 		r = self.client.post(path=url_for('invite.signup_submit', invite_id=invite.id, token=invite.token),
@@ -671,7 +671,7 @@ class TestInviteUseViews(UffdTestCase):
 		self.assertEqual(r.status_code, 200)
 
 	def test_signup_invalid(self):
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
 		db.session.add(invite)
 		db.session.commit()
 		r = self.client.post(path=url_for('invite.signup_submit', invite_id=invite.id, token=invite.token),
@@ -682,7 +682,7 @@ class TestInviteUseViews(UffdTestCase):
 
 	def test_signup_mailerror(self):
 		self.app.config['MAIL_SKIP_SEND'] = 'fail'
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
 		db.session.add(invite)
 		db.session.commit()
 		r = self.client.post(path=url_for('invite.signup_submit', invite_id=invite.id, token=invite.token),
@@ -692,7 +692,7 @@ class TestInviteUseViews(UffdTestCase):
 		self.assertEqual(r.status_code, 200)
 
 	def test_signup_hostlimit(self):
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
 		db.session.add(invite)
 		db.session.commit()
 		invite_id = invite.id
@@ -712,7 +712,7 @@ class TestInviteUseViews(UffdTestCase):
 		self.assertIsNone(self.app.last_mail)
 
 	def test_signup_mailimit(self):
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
 		db.session.add(invite)
 		db.session.commit()
 		invite_id = invite.id
@@ -732,7 +732,7 @@ class TestInviteUseViews(UffdTestCase):
 		self.assertIsNone(self.app.last_mail)
 
 	def test_signup_check(self):
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
 		db.session.add(invite)
 		db.session.commit()
 		invite_id = invite.id
@@ -744,7 +744,7 @@ class TestInviteUseViews(UffdTestCase):
 		self.assertEqual(r.json['status'], 'ok')
 
 	def test_signup_check_invalid(self):
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
 		db.session.add(invite)
 		db.session.commit()
 		invite_id = invite.id
@@ -757,7 +757,7 @@ class TestInviteUseViews(UffdTestCase):
 		self.assertEqual(r.json['status'], 'invalid')
 
 	def test_signup_check_exists(self):
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
 		db.session.add(invite)
 		db.session.commit()
 		invite_id = invite.id
@@ -769,7 +769,7 @@ class TestInviteUseViews(UffdTestCase):
 		self.assertEqual(r.json['status'], 'exists')
 
 	def test_signup_check_nosignup(self):
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=False, creator=self.get_admin())
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=False, creator=self.get_admin())
 		db.session.add(invite)
 		db.session.commit()
 		invite_id = invite.id
@@ -781,7 +781,7 @@ class TestInviteUseViews(UffdTestCase):
 		self.assertEqual(r.json['status'], 'error')
 
 	def test_signup_check_error(self):
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True, disabled=True, creator=self.get_admin())
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, disabled=True, creator=self.get_admin())
 		db.session.add(invite)
 		db.session.commit()
 		invite_id = invite.id
@@ -793,7 +793,7 @@ class TestInviteUseViews(UffdTestCase):
 		self.assertEqual(r.json['status'], 'error')
 
 	def test_signup_check_ratelimited(self):
-		invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
+		invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
 		db.session.add(invite)
 		db.session.commit()
 		invite_id = invite.id
diff --git a/tests/test_mfa.py b/tests/test_mfa.py
index ff32a105fc9028a4f0c2ba77bfa7fe27d3590486..f1e254a3bcebe483c7a163edc4d187f4c006db94 100644
--- a/tests/test_mfa.py
+++ b/tests/test_mfa.py
@@ -31,7 +31,7 @@ def get_fido2_test_cred(self):
 class TestMfaMethodModels(UffdTestCase):
 	def test_common_attributes(self):
 		method = TOTPMethod(user=self.get_user(), name='testname')
-		self.assertTrue(method.created <= datetime.datetime.now())
+		self.assertTrue(method.created <= datetime.datetime.utcnow())
 		self.assertEqual(method.name, 'testname')
 		self.assertEqual(method.user.loginname, 'testuser')
 		method.user = self.get_admin()
diff --git a/tests/test_selfservice.py b/tests/test_selfservice.py
index cc2b0a86a3341ab62d5a6e9deebabbde8bf46e97..760b7a449d9a30db0786201683a003f30584d7d8 100644
--- a/tests/test_selfservice.py
+++ b/tests/test_selfservice.py
@@ -186,7 +186,7 @@ class TestSelfservice(UffdTestCase):
 		user = request.user
 		old_mail = user.mail
 		token = MailToken(user=user, newmail='newusermail@example.com',
-			created=(datetime.datetime.now() - datetime.timedelta(days=10)))
+			created=(datetime.datetime.utcnow() - datetime.timedelta(days=10)))
 		db.session.add(token)
 		db.session.commit()
 		r = self.client.get(path=url_for('selfservice.token_mail', token_id=token.id, token=token.token), follow_redirects=True)
@@ -288,7 +288,7 @@ class TestSelfservice(UffdTestCase):
 
 	def test_token_password_expired(self):
 		user = self.get_user()
-		token = PasswordToken(user=user, created=(datetime.datetime.now() - datetime.timedelta(days=10)))
+		token = PasswordToken(user=user, created=(datetime.datetime.utcnow() - datetime.timedelta(days=10)))
 		db.session.add(token)
 		db.session.commit()
 		self.assertTrue(token.expired)
diff --git a/tests/test_signup.py b/tests/test_signup.py
index e77c7c4678a344a6deb826d5bfa5e87d243999e0..cfb599d5c84e6307cc0cb0910ee5921907c2c18a 100644
--- a/tests/test_signup.py
+++ b/tests/test_signup.py
@@ -61,7 +61,7 @@ class TestSignupModel(UffdTestCase):
 		# TODO: Find a better way to test this!
 		signup = Signup(loginname='newuser', displayname='New User', mail='test@example.com', password='notsecret')
 		self.assertFalse(signup.expired)
-		signup.created = created=datetime.datetime.now() - datetime.timedelta(hours=49)
+		signup.created = created=datetime.datetime.utcnow() - datetime.timedelta(hours=49)
 		self.assertTrue(signup.expired)
 
 	def test_completed(self):
@@ -86,7 +86,7 @@ class TestSignupModel(UffdTestCase):
 
 	def test_validate_expired(self):
 		signup = Signup(loginname='newuser', displayname='New User', mail='test@example.com',
-		                password='notsecret', created=datetime.datetime.now()-datetime.timedelta(hours=49))
+		                password='notsecret', created=datetime.datetime.utcnow()-datetime.timedelta(hours=49))
 		self.assert_validate_invalid(signup)
 		self.assert_validate_invalid(refetch_signup(signup))
 
@@ -132,7 +132,7 @@ class TestSignupModel(UffdTestCase):
 	def test_finish_expired(self):
 		# TODO: Find a better way to test this!
 		signup = Signup(loginname='newuser', displayname='New User', mail='test@example.com',
-		                password='notsecret', created=datetime.datetime.now()-datetime.timedelta(hours=49))
+		                password='notsecret', created=datetime.datetime.utcnow()-datetime.timedelta(hours=49))
 		self.assert_finish_failure(signup, 'notsecret')
 		self.assert_finish_failure(refetch_signup(signup), 'notsecret')
 
@@ -366,7 +366,7 @@ class TestSignupViews(UffdTestCase):
 
 	def test_confirm_expired(self):
 		signup = Signup(loginname='newuser', displayname='New User', mail='test@example.com', password='notsecret')
-		signup.created = datetime.datetime.now() - datetime.timedelta(hours=49)
+		signup.created = datetime.datetime.utcnow() - datetime.timedelta(hours=49)
 		signup = refetch_signup(signup)
 		r = self.client.get(path=url_for('signup.signup_confirm', signup_id=signup.id, token=signup.token), follow_redirects=True)
 		dump('test_signup_confirm_expired', r)
diff --git a/uffd/__init__.py b/uffd/__init__.py
index d178c85f9d4e6f7f61fffe7447d83b31ca98359e..ad186dee28e1e6223661add3014988f61c3e02f4 100644
--- a/uffd/__init__.py
+++ b/uffd/__init__.py
@@ -4,6 +4,7 @@ import sys
 
 from flask import Flask, redirect, url_for, request, render_template
 from flask_babel import Babel
+from babel.dates import LOCALTZ
 from werkzeug.exceptions import Forbidden
 from flask_migrate import Migrate
 
@@ -86,7 +87,21 @@ def create_app(test_config=None): # pylint: disable=too-many-locals,too-many-sta
 	def push_request_context(): #pylint: disable=unused-variable
 		return {'db': db} | {name: getattr(models, name) for name in models.__all__}
 
-	babel = Babel(app)
+	# flask-babel requires pytz-style timezone objects, but in rare cases (e.g.
+	# non-IANA TZ values) LOCALTZ is stdlib-style (without normalize/localize)
+	if not hasattr(LOCALTZ, 'normalize'):
+		LOCALTZ.normalize = lambda dt: dt
+	if not hasattr(LOCALTZ, 'localize'):
+		LOCALTZ.localize = lambda dt: dt.replace(tzinfo=LOCALTZ)
+
+	class PatchedBabel(Babel):
+		@property
+		def default_timezone(self):
+			if self.app.config['BABEL_DEFAULT_TIMEZONE'] == 'LOCALTZ':
+				return LOCALTZ
+			return super().default_timezone
+
+	babel = PatchedBabel(app, default_timezone='LOCALTZ')
 
 	@babel.localeselector
 	def get_locale(): #pylint: disable=unused-variable
diff --git a/uffd/migrations/versions/9f824f61d8ac_use_utc_for_datetime.py b/uffd/migrations/versions/9f824f61d8ac_use_utc_for_datetime.py
new file mode 100644
index 0000000000000000000000000000000000000000..f52906178252d2b10f66ca3c797d8b5c247b26aa
--- /dev/null
+++ b/uffd/migrations/versions/9f824f61d8ac_use_utc_for_datetime.py
@@ -0,0 +1,111 @@
+"""Use UTC for DateTime
+
+Revision ID: 9f824f61d8ac
+Revises: a60ce68b9214
+Create Date: 2022-08-16 00:51:04.635182
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+# revision identifiers, used by Alembic.
+revision = '9f824f61d8ac'
+down_revision = 'a60ce68b9214'
+branch_labels = None
+depends_on = None
+
+import datetime
+
+def localtime_to_utc(dt):
+	return dt.astimezone(datetime.timezone.utc).replace(tzinfo=None)
+
+def utc_to_localtime(dt):
+	return dt.replace(tzinfo=datetime.timezone.utc).astimezone().replace(tzinfo=None)
+
+def iter_rows_paged(table, pk='id', limit=1000):
+	conn = op.get_bind()
+	pk_column = getattr(table.c, pk)
+	last_pk = None
+	while True:
+		expr = table.select().order_by(pk_column).limit(limit)
+		if last_pk is not None:
+			expr = expr.where(pk_column > last_pk)
+		result = conn.execute(expr)
+		pk_index = result.keys().index(pk)
+		rows = result.fetchall()
+		if not rows:
+			break
+		yield from rows
+		last_pk = rows[-1][pk_index]
+
+invite = sa.table('invite',
+	sa.column('id', sa.Integer),
+	sa.column('created', sa.DateTime),
+	sa.column('valid_until', sa.DateTime),
+)
+
+password_token = sa.table('passwordToken',
+	sa.column('id', sa.Integer),
+	sa.column('created', sa.DateTime),
+)
+
+mail_token = sa.table('mailToken',
+	sa.column('id', sa.Integer),
+	sa.column('created', sa.DateTime),
+)
+
+device_login_initiation = sa.table('device_login_initiation',
+	sa.column('id', sa.Integer),
+	sa.column('created', sa.DateTime),
+)
+
+signup = sa.table('signup',
+	sa.column('id', sa.Integer),
+	sa.column('created', sa.DateTime),
+)
+
+def upgrade():
+	for obj_id, created, valid_until in iter_rows_paged(invite):
+		op.execute(invite.update().where(invite.c.id==obj_id).values(
+			created=localtime_to_utc(created),
+			valid_until=localtime_to_utc(valid_until),
+		))
+	for obj_id, created in iter_rows_paged(password_token):
+		op.execute(password_token.update().where(password_token.c.id==obj_id).values(
+			created=localtime_to_utc(created),
+		))
+	for obj_id, created in iter_rows_paged(mail_token):
+		op.execute(mail_token.update().where(mail_token.c.id==obj_id).values(
+			created=localtime_to_utc(created),
+		))
+	for obj_id, created in iter_rows_paged(device_login_initiation):
+		op.execute(device_login_initiation.update().where(device_login_initiation.c.id==obj_id).values(
+			created=localtime_to_utc(created),
+		))
+	for obj_id, created in iter_rows_paged(signup):
+		op.execute(signup.update().where(signup.c.id==obj_id).values(
+			created=localtime_to_utc(created),
+		))
+
+def downgrade():
+	for obj_id, created, valid_until in iter_rows_paged(invite):
+		op.execute(invite.update().where(invite.c.id==obj_id).values(
+			created=utc_to_localtime(created),
+			valid_until=utc_to_localtime(valid_until),
+		))
+	for obj_id, created in iter_rows_paged(password_token):
+		op.execute(password_token.update().where(password_token.c.id==obj_id).values(
+			created=utc_to_localtime(created),
+		))
+	for obj_id, created in iter_rows_paged(mail_token):
+		op.execute(mail_token.update().where(mail_token.c.id==obj_id).values(
+			created=utc_to_localtime(created),
+		))
+	for obj_id, created in iter_rows_paged(device_login_initiation):
+		op.execute(device_login_initiation.update().where(device_login_initiation.c.id==obj_id).values(
+			created=utc_to_localtime(created),
+		))
+	for obj_id, created in iter_rows_paged(signup):
+		op.execute(signup.update().where(signup.c.id==obj_id).values(
+			created=utc_to_localtime(created),
+		))
diff --git a/uffd/models/invite.py b/uffd/models/invite.py
index 12018d35ca11089c70e31da611de5d3b5060727f..ad550b256f8bba4e46f6f9eee833b319396ec2de 100644
--- a/uffd/models/invite.py
+++ b/uffd/models/invite.py
@@ -18,7 +18,7 @@ class Invite(db.Model):
 	__tablename__ = 'invite'
 	id = Column(Integer(), primary_key=True, autoincrement=True)
 	token = Column(String(128), unique=True, nullable=False, default=token_urlfriendly)
-	created = Column(DateTime, default=datetime.datetime.now, nullable=False)
+	created = Column(DateTime, default=datetime.datetime.utcnow, nullable=False)
 	creator_id = Column(Integer(), ForeignKey('user.id', onupdate='CASCADE'), nullable=True)
 	creator = relationship('User')
 	valid_until = Column(DateTime, nullable=False)
@@ -32,7 +32,7 @@ class Invite(db.Model):
 
 	@property
 	def expired(self):
-		return datetime.datetime.now().replace(second=0, microsecond=0) > self.valid_until
+		return datetime.datetime.utcnow().replace(second=0, microsecond=0) > self.valid_until
 
 	@property
 	def voided(self):
diff --git a/uffd/models/mfa.py b/uffd/models/mfa.py
index 7db21b5cd3f97ffad9da32c4f41d931ce1b26180..9d2b3458894491c8e45ac1038b370837fc8f5239 100644
--- a/uffd/models/mfa.py
+++ b/uffd/models/mfa.py
@@ -29,7 +29,7 @@ class MFAMethod(db.Model):
 	__tablename__ = 'mfa_method'
 	id = Column(Integer(), primary_key=True, autoincrement=True)
 	type = Column(Enum(MFAType), nullable=False)
-	created = Column(DateTime(), nullable=False, default=datetime.datetime.now)
+	created = Column(DateTime(), nullable=False, default=datetime.datetime.utcnow)
 	name = Column(String(128))
 	user_id = Column(Integer(), ForeignKey('user.id', onupdate='CASCADE', ondelete='CASCADE'), nullable=False)
 	user = relationship('User', backref=backref('mfa_methods', cascade='all, delete-orphan'))
@@ -41,7 +41,7 @@ class MFAMethod(db.Model):
 	def __init__(self, user, name=None):
 		self.user = user
 		self.name = name
-		self.created = datetime.datetime.now()
+		self.created = datetime.datetime.utcnow()
 
 class RecoveryCodeMethod(MFAMethod):
 	code_salt = Column('recovery_salt', String(64))
diff --git a/uffd/models/selfservice.py b/uffd/models/selfservice.py
index bb1484579e70b27f948d9b55638f25c124ef018c..5c4d5eb1079bb26ddf1ff05d5b501f58f8a1a644 100644
--- a/uffd/models/selfservice.py
+++ b/uffd/models/selfservice.py
@@ -13,7 +13,7 @@ class PasswordToken(db.Model):
 	__tablename__ = 'passwordToken'
 	id = Column(Integer(), primary_key=True, autoincrement=True)
 	token = Column(String(128), default=token_urlfriendly, nullable=False)
-	created = Column(DateTime, default=datetime.datetime.now, nullable=False)
+	created = Column(DateTime, default=datetime.datetime.utcnow, nullable=False)
 	user_id = Column(Integer(), ForeignKey('user.id', onupdate='CASCADE', ondelete='CASCADE'), nullable=False)
 	user = relationship('User')
 
@@ -21,14 +21,14 @@ class PasswordToken(db.Model):
 	def expired(self):
 		if self.created is None:
 			return False
-		return self.created < datetime.datetime.now() - datetime.timedelta(days=2)
+		return self.created < datetime.datetime.utcnow() - datetime.timedelta(days=2)
 
 @cleanup_task.delete_by_attribute('expired')
 class MailToken(db.Model):
 	__tablename__ = 'mailToken'
 	id = Column(Integer(), primary_key=True, autoincrement=True)
 	token = Column(String(128), default=token_urlfriendly, nullable=False)
-	created = Column(DateTime, default=datetime.datetime.now)
+	created = Column(DateTime, default=datetime.datetime.utcnow)
 	user_id = Column(Integer(), ForeignKey('user.id', onupdate='CASCADE', ondelete='CASCADE'), nullable=False)
 	user = relationship('User')
 	newmail = Column(String(255))
@@ -37,4 +37,4 @@ class MailToken(db.Model):
 	def expired(self):
 		if self.created is None:
 			return False
-		return self.created < datetime.datetime.now() - datetime.timedelta(days=2)
+		return self.created < datetime.datetime.utcnow() - datetime.timedelta(days=2)
diff --git a/uffd/models/session.py b/uffd/models/session.py
index 6b72912b08c0b7a068a46ad3e0932bd055f6d2c3..19fa3ad868af7cc171903692ed0ebdb805bd7974 100644
--- a/uffd/models/session.py
+++ b/uffd/models/session.py
@@ -80,7 +80,7 @@ class DeviceLoginInitiation(db.Model):
 	code1 = Column(String(32), unique=True, nullable=False, default=lambda: token_typeable(3))
 	secret = Column(String(128), nullable=False, default=lambda: secrets.token_hex(64))
 	confirmations = relationship('DeviceLoginConfirmation', back_populates='initiation', cascade='all, delete-orphan')
-	created = Column(DateTime, default=datetime.datetime.now, nullable=False)
+	created = Column(DateTime, default=datetime.datetime.utcnow, nullable=False)
 
 	__mapper_args__ = {
 		'polymorphic_on': type,
@@ -96,7 +96,7 @@ class DeviceLoginInitiation(db.Model):
 	def expired(self):
 		if self.created is None:
 			return False
-		return self.created < datetime.datetime.now() - datetime.timedelta(minutes=30)
+		return self.created < datetime.datetime.utcnow() - datetime.timedelta(minutes=30)
 
 	@property
 	def description(self):
diff --git a/uffd/models/signup.py b/uffd/models/signup.py
index 204edbc416d1c0b8c571499f12cb1a50454b6c5a..c04dbb41731db2f65c13795137018b4f5a0859b6 100644
--- a/uffd/models/signup.py
+++ b/uffd/models/signup.py
@@ -31,7 +31,7 @@ class Signup(db.Model):
 	__tablename__ = 'signup'
 	id = Column(Integer(), primary_key=True, autoincrement=True)
 	token = Column(String(128), default=token_urlfriendly, nullable=False)
-	created = Column(DateTime, default=datetime.datetime.now, nullable=False)
+	created = Column(DateTime, default=datetime.datetime.utcnow, nullable=False)
 	loginname = Column(Text)
 	displayname = Column(Text)
 	mail = Column(Text)
@@ -56,7 +56,7 @@ class Signup(db.Model):
 	def expired(self):
 		if self.created is None:
 			return False
-		return self.created < datetime.datetime.now() - datetime.timedelta(hours=48)
+		return self.created < datetime.datetime.utcnow() - datetime.timedelta(hours=48)
 
 	@hybrid_property
 	def completed(self):
diff --git a/uffd/templates/invite/list.html b/uffd/templates/invite/list.html
index 9a179a87ce7d48ac67360c8674097bbc0f103713..97c716230a7eed3730010909cdf61296649f069f 100644
--- a/uffd/templates/invite/list.html
+++ b/uffd/templates/invite/list.html
@@ -56,9 +56,9 @@
 					{% elif not invite.active %}
 						{{_('Invalid')}}
 					{% elif invite.single_use %}
-						{{ _('Valid once, expires %(expiry_date)s', expiry_date=invite.valid_until.strftime('%Y-%m-%d')) }}
+						{{ _('Valid once, expires %(expiry_date)s', expiry_date=invite.valid_until|dateformat) }}
 					{% else %}
-						{{ _('Valid, expires %(expiry_date)s',expiry_date=invite.valid_until.strftime('%Y-%m-%d')) }}
+						{{ _('Valid, expires %(expiry_date)s', expiry_date=invite.valid_until|dateformat) }}
 					{% endif %}
 				</td>
 				<td class="text-right">
@@ -83,8 +83,8 @@
 			<div class="modal-body">
 				<ul class="list-unstyled">
 					<li><b>{{_('Type:')}}</b> {% if invite.single_use %}{{_('Single-use')}}{% else %}{{_('Multi-use')}}{% endif %}</li>
-					<li><b>{{_('Created:')}}</b> {{ invite.created.strftime('%Y-%m-%d %H:%M:%S') }}</li>
-					<li><b>{{_('Expires:')}}</b> {{ invite.valid_until.strftime('%Y-%m-%d %H:%M:%S') }}</li>
+					<li><b>{{_('Created:')}}</b> {{ invite.created|datetimeformat }}</li>
+					<li><b>{{_('Expires:')}}</b> {{ invite.valid_until|datetimeformat }}</li>
 					<li><b>{{_('Permissions:')}}</b>
 						<ul>
 							{% if invite.allow_signup %}
diff --git a/uffd/templates/mfa/setup.html b/uffd/templates/mfa/setup.html
index 5d5aefaa2be740e73c3957db6a79bb3cd179e36a..2922aac2862f5a6ebee75735d7cb8d97c040daf2 100644
--- a/uffd/templates/mfa/setup.html
+++ b/uffd/templates/mfa/setup.html
@@ -113,7 +113,7 @@ mfa_enabled: The user has setup at least one two-factor method. Two-factor authe
 				{% for method in request.user.mfa_totp_methods %}
 				<tr>
 					<td>{{ method.name }}</td>
-					<td>{{ method.created.strftime('%b %d, %Y') }}</td>
+					<td>{{ method.created|dateformat }}</td>
 					<td><a class="btn btn-sm btn-danger float-right" href="{{ url_for('mfa.delete_totp', id=method.id) }}">{{_("Delete")}}</a></td>
 				</tr>
 				{% endfor %}
@@ -175,7 +175,7 @@ mfa_enabled: The user has setup at least one two-factor method. Two-factor authe
 				{% for method in request.user.mfa_webauthn_methods %}
 				<tr>
 					<td>{{ method.name }}</td>
-					<td>{{ method.created.strftime('%b %d, %Y') }}</td>
+					<td>{{ method.created|dateformat }}</td>
 					<td><a class="btn btn-sm btn-danger float-right" href="{{ url_for('mfa.delete_webauthn', id=method.id) }}">{{_("Delete")}}</a></td>
 				</tr>
 				{% endfor %}
diff --git a/uffd/views/invite.py b/uffd/views/invite.py
index 99b0675ed57ac571d90b38b13a097f170e5b1738..d05848b30e2f26abb336eeff3462b3d2e12ad65d 100644
--- a/uffd/views/invite.py
+++ b/uffd/views/invite.py
@@ -2,7 +2,7 @@ import datetime
 import secrets
 
 from flask import Blueprint, render_template, request, redirect, url_for, flash, current_app, jsonify, abort
-from flask_babel import gettext as _, lazy_gettext
+from flask_babel import gettext as _, lazy_gettext, to_utc
 import sqlalchemy
 
 from uffd.csrf import csrf_protect
@@ -57,18 +57,21 @@ def new():
 		roles = Role.query.join(Role.moderator_group).join(Group.members).filter(User.id==request.user.id).all()
 	return render_template('invite/new.html', roles=roles, allow_signup=allow_signup)
 
+def parse_datetime_local_input(value):
+	return to_utc(datetime.datetime.fromisoformat(value))
+
 @bp.route('/new', methods=['POST'])
 @login_required(invite_acl_check)
 @csrf_protect(blueprint=bp)
 def new_submit():
 	invite = Invite(creator=request.user,
 	                single_use=(request.values['single-use'] == '1'),
-	                valid_until=datetime.datetime.fromisoformat(request.values['valid-until']),
+	                valid_until=parse_datetime_local_input(request.values['valid-until']),
 	                allow_signup=(request.values.get('allow-signup', '0') == '1'))
 	for key, value in request.values.items():
 		if key.startswith('role-') and value == '1':
 			invite.roles.append(Role.query.get(key[5:]))
-	if invite.valid_until > datetime.datetime.now() + datetime.timedelta(days=current_app.config['INVITE_MAX_VALID_DAYS']):
+	if invite.valid_until > datetime.datetime.utcnow() + datetime.timedelta(days=current_app.config['INVITE_MAX_VALID_DAYS']):
 		flash(_('The "Expires After" date is too far in the future'))
 		return new()
 	if not invite.permitted:
diff --git a/uffd/views/session.py b/uffd/views/session.py
index 3f6a6187452e79193b715953608f2d1bfa58c1d7..9e45c210220a110de38d83a91e06e9a95de7d814 100644
--- a/uffd/views/session.py
+++ b/uffd/views/session.py
@@ -22,7 +22,7 @@ def set_request_user():
 		return
 	if 'logintime' not in session:
 		return
-	if datetime.datetime.now().timestamp() > session['logintime'] + current_app.config['SESSION_LIFETIME_SECONDS']:
+	if datetime.datetime.utcnow().timestamp() > session['logintime'] + current_app.config['SESSION_LIFETIME_SECONDS']:
 		return
 	user = User.query.get(session['user_id'])
 	if not user or not user.is_in_group(current_app.config['ACL_ACCESS_GROUP']):
@@ -49,7 +49,7 @@ def set_session(user, skip_mfa=False):
 	session.clear()
 	session.permanent = True
 	session['user_id'] = user.id
-	session['logintime'] = datetime.datetime.now().timestamp()
+	session['logintime'] = datetime.datetime.utcnow().timestamp()
 	session['_csrf_token'] = secrets.token_hex(128)
 	if skip_mfa:
 		session['user_mfa'] = True