From baee01063baec071f496a93c59e14b565acfa25f Mon Sep 17 00:00:00 2001 From: Laurent Date: Mon, 21 Oct 2024 17:18:01 +0200 Subject: [PATCH 1/6] update mail test --- tournaments/static/misc/jap-test.csv | 2 -- tournaments/views.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/tournaments/static/misc/jap-test.csv b/tournaments/static/misc/jap-test.csv index 04d8fde..f32ae23 100644 --- a/tournaments/static/misc/jap-test.csv +++ b/tournaments/static/misc/jap-test.csv @@ -1,3 +1 @@ CAN PADEL,POPOVITCH,Laurent,laurent@padelclub.app,0629445485 -CAN PADEL,POPOVITCH,Razmig,razmig@padelclub.app,0629445485 -CAN PADEL,POPOVITCH,Xavier,xavier@padelclub.app,0629445485 diff --git a/tournaments/views.py b/tournaments/views.py index 3904f9d..367b32c 100644 --- a/tournaments/views.py +++ b/tournaments/views.py @@ -484,7 +484,7 @@ def send_email(mail, name): name = "" subject = "Tes tournois en toute simplicité avec Padel Club" - body = f"Salut {name} !\n\nJe me permets de t'écrire car je suis JAP2 en région PACA et développeur, et je viens de lancer Padel Club, une app iOS qui facilite enfin l'organisation des tournois. Avec elle, tu peux convoquer rapidement, simuler et programmer tes structures, diffuser tous les résultats à tous les joueurs, et ce depuis un iPhone.\n\nTu peux l'essayer gratuitement pour découvrir tout son potentiel ! Télécharge l'app ici et teste la dès ton prochain tournoi: https://padelclub.app/download/\n\nJe suis disponible pour échanger avec toi par mail ou téléphone au 06 81 59 81 93 et voir ce que tu en penses.\nÀ bientôt j'espère !\n\nRazmig" + body = f"Salut {name} !\n\nJe me permets de t'écrire car je suis JAP2 en région PACA et développeur, et je viens de lancer Padel Club, une app iOS qui facilite enfin l'organisation des tournois.\n\nAvec elle, tu peux convoquer rapidement, simuler et programmer tes structures, diffuser tous les résultats à tous les joueurs, et ce depuis un iPhone.\n\nTu peux l'essayer gratuitement pour découvrir tout son potentiel ! Télécharge l'app ici et teste la dès ton prochain tournoi: https://padelclub.app/download/\n\nJe suis disponible pour échanger avec toi par mail ou téléphone au 06 81 59 81 93 et voir ce que tu en penses.\nÀ bientôt j'espère !\n\nRazmig" email = EmailMessage(subject, body, to=[mail]) email.send() From 97c9cf32cd0a91ec444848aa9cdba8fb1a857d93 Mon Sep 17 00:00:00 2001 From: Raz Date: Thu, 24 Oct 2024 08:37:43 +0200 Subject: [PATCH 2/6] fix court and startdate display when not confirmed --- tournaments/models/match.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tournaments/models/match.py b/tournaments/models/match.py index 4e4695d..9dfda0d 100644 --- a/tournaments/models/match.py +++ b/tournaments/models/match.py @@ -41,6 +41,8 @@ class Match(models.Model): if self.tournament().event: club = self.tournament().event.club + if self.confirmed is False: + return "" if club: return club.court_name(index) elif index is not None: @@ -95,10 +97,16 @@ class Match(models.Model): return '' elif self.start_date: if self.started(): - return self.formatted_duration() + if self.confirmed: + return self.formatted_duration() + else: + return 'À suivre' else: timezoned_datetime = timezone.localtime(self.start_date) - return formats.date_format(timezoned_datetime, format='l H:i') + if self.confirmed: + return formats.date_format(timezoned_datetime, format='l H:i') + else: + return f"Estimée : {formats.date_format(timezoned_datetime, format='l H:i')}" else: return 'À venir...' From bd941387c2cec12f3a84f2f05f7c85eb7af39af3 Mon Sep 17 00:00:00 2001 From: Laurent Date: Thu, 24 Oct 2024 12:09:31 +0200 Subject: [PATCH 3/6] adds timezone for clubs --- tournaments/models/club.py | 7 ++++++- tournaments/models/match.py | 9 ++++++--- tournaments/models/tournament.py | 9 +++++++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/tournaments/models/club.py b/tournaments/models/club.py index d2b2f29..1a13cfe 100644 --- a/tournaments/models/club.py +++ b/tournaments/models/club.py @@ -1,4 +1,5 @@ from django.db import models +from zoneinfo import available_timezones import uuid class Club(models.Model): @@ -15,7 +16,11 @@ class Club(models.Model): zip_code = models.CharField(max_length=10, null=True, blank=True) latitude = models.FloatField(null=True, blank=True) longitude = models.FloatField(null=True, blank=True) - + timezone = models.CharField( + max_length=50, + choices=[(tz, tz) for tz in sorted(available_timezones())], + default='CET' + ) court_count = models.IntegerField(default=2) broadcast_code = models.CharField(max_length=10, null=True, blank=True, unique=True) diff --git a/tournaments/models/match.py b/tournaments/models/match.py index 4e4695d..6a0d954 100644 --- a/tournaments/models/match.py +++ b/tournaments/models/match.py @@ -2,7 +2,8 @@ from django.db import models from tournaments.models import group_stage from . import Round, GroupStage, FederalMatchCategory from django.utils import timezone, formats -from datetime import timedelta +from datetime import datetime, timedelta + import uuid from ..utils.extensions import format_seconds @@ -80,8 +81,10 @@ class Match(models.Model): def formatted_start_date(self): if self.start_date: - timezoned_datetime = timezone.localtime(self.start_date) - return formats.date_format(timezoned_datetime, format='H:i') + timezone = self.tournament().timezone() + local_start = self.start_date.astimezone(timezone) + # timezoned_datetime = timezone.localtime(self.start_date) + return formats.date_format(local_start, format='H:i') # return formats.date_format(self.start_date, format='H:i') else: return '' diff --git a/tournaments/models/tournament.py b/tournaments/models/tournament.py index bb1c6e3..9e47813 100644 --- a/tournaments/models/tournament.py +++ b/tournaments/models/tournament.py @@ -1,3 +1,4 @@ +from zoneinfo import ZoneInfo from django.db import models from typing import TYPE_CHECKING if TYPE_CHECKING: @@ -7,6 +8,8 @@ from . import Event, TournamentPayment, FederalMatchCategory, FederalCategory, F import uuid from django.utils import timezone, formats from datetime import datetime, timedelta +from zoneinfo import ZoneInfo + from shared.cryptography import encryption_util from ..utils.extensions import plural_format @@ -129,6 +132,12 @@ class Tournament(models.Model): components.append(self.name) return (' ').join(components) + def timezone(self): + tz = 'CET' + if self.event and self.event.club: + tz = self.event.club.timezone + return ZoneInfo(tz) + def level(self): if self.federal_level_category == 0: return "Anim." From 6517f772a3c4e29c750fdc99073848fb3336ebfc Mon Sep 17 00:00:00 2001 From: Raz Date: Thu, 24 Oct 2024 12:35:28 +0200 Subject: [PATCH 4/6] add drawlog class --- api/serializers.py | 6 ++++++ api/urls.py | 1 + api/views.py | 15 ++++++++++++++- tournaments/admin.py | 7 +++++++ tournaments/migrations/0091_drawlog.py | 26 ++++++++++++++++++++++++++ tournaments/models/draw_log.py | 10 ++++++++++ 6 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 tournaments/migrations/0091_drawlog.py create mode 100644 tournaments/models/draw_log.py diff --git a/api/serializers.py b/api/serializers.py index d5b44f3..0057fdf 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -12,6 +12,7 @@ from django.contrib.sites.shortcuts import get_current_site from api.tokens import account_activation_token from shared.cryptography import encryption_util +from tournaments.models.draw_log import DrawLog class EncryptedUserField(serializers.Field): def to_representation(self, value): @@ -230,3 +231,8 @@ class DeviceTokenSerializer(serializers.ModelSerializer): model = DeviceToken fields = '__all__' read_only_fields = ['user'] + +class DrawLogSerializer(serializers.ModelSerializer): + class Meta: + model = DrawLog + fields = '__all__' diff --git a/api/urls.py b/api/urls.py index ff61351..b221163 100644 --- a/api/urls.py +++ b/api/urls.py @@ -18,6 +18,7 @@ router.register(r'player-registrations', views.PlayerRegistrationViewSet) router.register(r'purchases', views.PurchaseViewSet) router.register(r'courts', views.CourtViewSet) router.register(r'date-intervals', views.DateIntervalViewSet) +router.register(r'draw-logs', views.DrawLogViewSet) router.register(r'failed-api-calls', views.FailedApiCallViewSet) router.register(r'logs', views.LogViewSet) router.register(r'device-token', views.DeviceTokenViewSet) diff --git a/api/views.py b/api/views.py index a8d84b5..f004f5d 100644 --- a/api/views.py +++ b/api/views.py @@ -1,5 +1,6 @@ from pandas.io.feather_format import pd -from .serializers import ClubSerializer, CourtSerializer, DateIntervalSerializer, TournamentSerializer, UserSerializer, ChangePasswordSerializer, EventSerializer, RoundSerializer, GroupStageSerializer, MatchSerializer, TeamScoreSerializer, TeamRegistrationSerializer, PlayerRegistrationSerializer, LiveMatchSerializer, PurchaseSerializer, UserUpdateSerializer, FailedApiCallSerializer, LogSerializer, DeviceTokenSerializer +from tournaments.models.draw_log import DrawLog +from .serializers import ClubSerializer, CourtSerializer, DateIntervalSerializer, DrawLogSerializer, TournamentSerializer, UserSerializer, ChangePasswordSerializer, EventSerializer, RoundSerializer, GroupStageSerializer, MatchSerializer, TeamScoreSerializer, TeamRegistrationSerializer, PlayerRegistrationSerializer, LiveMatchSerializer, PurchaseSerializer, UserUpdateSerializer, FailedApiCallSerializer, LogSerializer, DeviceTokenSerializer from tournaments.models import Club, Tournament, CustomUser, Event, Round, GroupStage, Match, TeamScore, TeamRegistration, PlayerRegistration, Court, DateInterval, Purchase, FailedApiCall, Log, DeviceToken from rest_framework import viewsets, permissions @@ -288,3 +289,15 @@ class DeviceTokenViewSet(viewsets.ModelViewSet): def perform_create(self, serializer): serializer.save(user=self.request.user) + +class DrawLogViewSet(viewsets.ModelViewSet): + queryset = DrawLog.objects.all() + serializer_class = DrawLogSerializer + + def get_queryset(self): + tournament_id = self.request.query_params.get('tournament') + if tournament_id: + return self.queryset.filter(tournament=tournament_id) + if self.request.user: + return self.queryset.filter(tournament__event__creator=self.request.user) + return [] diff --git a/tournaments/admin.py b/tournaments/admin.py index ff0fc5d..41c923f 100644 --- a/tournaments/admin.py +++ b/tournaments/admin.py @@ -2,6 +2,7 @@ from django.contrib import admin from tournaments.models import team_registration from tournaments.models.device_token import DeviceToken +from tournaments.models.draw_log import DrawLog from .models import Club, TeamScore, Tournament, CustomUser, Event, Round, GroupStage, Match, TeamRegistration, PlayerRegistration, Purchase, Court, DateInterval, FailedApiCall, Log from django.contrib.auth.admin import UserAdmin @@ -105,6 +106,11 @@ class LogAdmin(admin.ModelAdmin): class DeviceTokenAdmin(admin.ModelAdmin): list_display = ['user', 'value'] +class DrawLogAdmin(admin.ModelAdmin): + list_display = ['tournament', 'draw_date', 'draw_seed', 'draw_match_index', 'draw_team_position'] + list_filter = [SimpleTournamentListFilter] + ordering = ['draw_date'] + admin.site.register(CustomUser, CustomUserAdmin) admin.site.register(Club, ClubAdmin) admin.site.register(Event, EventAdmin) @@ -121,3 +127,4 @@ admin.site.register(DateInterval, DateIntervalAdmin) admin.site.register(FailedApiCall, FailedApiCallAdmin) admin.site.register(Log, LogAdmin) admin.site.register(DeviceToken, DeviceTokenAdmin) +admin.site.register(DrawLog, DrawLogAdmin) diff --git a/tournaments/migrations/0091_drawlog.py b/tournaments/migrations/0091_drawlog.py new file mode 100644 index 0000000..285b871 --- /dev/null +++ b/tournaments/migrations/0091_drawlog.py @@ -0,0 +1,26 @@ +# Generated by Django 4.2.11 on 2024-10-24 06:55 + +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('tournaments', '0090_tournament_initial_seed_count_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='DrawLog', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('draw_date', models.DateTimeField()), + ('draw_seed', models.IntegerField()), + ('draw_match_index', models.IntegerField()), + ('draw_team_position', models.IntegerField()), + ('tournament', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tournaments.tournament')), + ], + ), + ] diff --git a/tournaments/models/draw_log.py b/tournaments/models/draw_log.py new file mode 100644 index 0000000..c09e099 --- /dev/null +++ b/tournaments/models/draw_log.py @@ -0,0 +1,10 @@ +from django.db import models +import uuid + +class DrawLog(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True) + tournament = models.ForeignKey('Tournament', on_delete=models.CASCADE) + draw_date = models.DateTimeField() + draw_seed = models.IntegerField() + draw_match_index = models.IntegerField() + draw_team_position = models.IntegerField() From fcf43d1084077411c9970e7dbc6f6a4fa80fdfc2 Mon Sep 17 00:00:00 2001 From: Laurent Date: Thu, 24 Oct 2024 14:54:45 +0200 Subject: [PATCH 5/6] Use club timezone for matchs, tournaments and summons --- tournaments/migrations/0091_club_timezone.py | 18 ++++++++++++ tournaments/models/club.py | 2 +- tournaments/models/match.py | 28 ++++++++++--------- tournaments/models/tournament.py | 12 ++++++-- .../broadcast/broadcasted_match.html | 2 +- .../templates/tournaments/match_cell.html | 4 +-- .../templates/tournaments/summon_row.html | 2 +- .../tournaments/tournament_info.html | 3 +- 8 files changed, 49 insertions(+), 22 deletions(-) create mode 100644 tournaments/migrations/0091_club_timezone.py diff --git a/tournaments/migrations/0091_club_timezone.py b/tournaments/migrations/0091_club_timezone.py new file mode 100644 index 0000000..9ed2961 --- /dev/null +++ b/tournaments/migrations/0091_club_timezone.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1 on 2024-10-24 12:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tournaments', '0090_tournament_initial_seed_count_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='club', + name='timezone', + field=models.CharField(blank=True, choices=[('Africa/Abidjan', 'Africa/Abidjan'), ('Africa/Accra', 'Africa/Accra'), ('Africa/Addis_Ababa', 'Africa/Addis_Ababa'), ('Africa/Algiers', 'Africa/Algiers'), ('Africa/Asmara', 'Africa/Asmara'), ('Africa/Asmera', 'Africa/Asmera'), ('Africa/Bamako', 'Africa/Bamako'), ('Africa/Bangui', 'Africa/Bangui'), ('Africa/Banjul', 'Africa/Banjul'), ('Africa/Bissau', 'Africa/Bissau'), ('Africa/Blantyre', 'Africa/Blantyre'), ('Africa/Brazzaville', 'Africa/Brazzaville'), ('Africa/Bujumbura', 'Africa/Bujumbura'), ('Africa/Cairo', 'Africa/Cairo'), ('Africa/Casablanca', 'Africa/Casablanca'), ('Africa/Ceuta', 'Africa/Ceuta'), ('Africa/Conakry', 'Africa/Conakry'), ('Africa/Dakar', 'Africa/Dakar'), ('Africa/Dar_es_Salaam', 'Africa/Dar_es_Salaam'), ('Africa/Djibouti', 'Africa/Djibouti'), ('Africa/Douala', 'Africa/Douala'), ('Africa/El_Aaiun', 'Africa/El_Aaiun'), ('Africa/Freetown', 'Africa/Freetown'), ('Africa/Gaborone', 'Africa/Gaborone'), ('Africa/Harare', 'Africa/Harare'), ('Africa/Johannesburg', 'Africa/Johannesburg'), ('Africa/Juba', 'Africa/Juba'), ('Africa/Kampala', 'Africa/Kampala'), ('Africa/Khartoum', 'Africa/Khartoum'), ('Africa/Kigali', 'Africa/Kigali'), ('Africa/Kinshasa', 'Africa/Kinshasa'), ('Africa/Lagos', 'Africa/Lagos'), ('Africa/Libreville', 'Africa/Libreville'), ('Africa/Lome', 'Africa/Lome'), ('Africa/Luanda', 'Africa/Luanda'), ('Africa/Lubumbashi', 'Africa/Lubumbashi'), ('Africa/Lusaka', 'Africa/Lusaka'), ('Africa/Malabo', 'Africa/Malabo'), ('Africa/Maputo', 'Africa/Maputo'), ('Africa/Maseru', 'Africa/Maseru'), ('Africa/Mbabane', 'Africa/Mbabane'), ('Africa/Mogadishu', 'Africa/Mogadishu'), ('Africa/Monrovia', 'Africa/Monrovia'), ('Africa/Nairobi', 'Africa/Nairobi'), ('Africa/Ndjamena', 'Africa/Ndjamena'), ('Africa/Niamey', 'Africa/Niamey'), ('Africa/Nouakchott', 'Africa/Nouakchott'), ('Africa/Ouagadougou', 'Africa/Ouagadougou'), ('Africa/Porto-Novo', 'Africa/Porto-Novo'), ('Africa/Sao_Tome', 'Africa/Sao_Tome'), ('Africa/Timbuktu', 'Africa/Timbuktu'), ('Africa/Tripoli', 'Africa/Tripoli'), ('Africa/Tunis', 'Africa/Tunis'), ('Africa/Windhoek', 'Africa/Windhoek'), ('America/Adak', 'America/Adak'), ('America/Anchorage', 'America/Anchorage'), ('America/Anguilla', 'America/Anguilla'), ('America/Antigua', 'America/Antigua'), ('America/Araguaina', 'America/Araguaina'), ('America/Argentina/Buenos_Aires', 'America/Argentina/Buenos_Aires'), ('America/Argentina/Catamarca', 'America/Argentina/Catamarca'), ('America/Argentina/ComodRivadavia', 'America/Argentina/ComodRivadavia'), ('America/Argentina/Cordoba', 'America/Argentina/Cordoba'), ('America/Argentina/Jujuy', 'America/Argentina/Jujuy'), ('America/Argentina/La_Rioja', 'America/Argentina/La_Rioja'), ('America/Argentina/Mendoza', 'America/Argentina/Mendoza'), ('America/Argentina/Rio_Gallegos', 'America/Argentina/Rio_Gallegos'), ('America/Argentina/Salta', 'America/Argentina/Salta'), ('America/Argentina/San_Juan', 'America/Argentina/San_Juan'), ('America/Argentina/San_Luis', 'America/Argentina/San_Luis'), ('America/Argentina/Tucuman', 'America/Argentina/Tucuman'), ('America/Argentina/Ushuaia', 'America/Argentina/Ushuaia'), ('America/Aruba', 'America/Aruba'), ('America/Asuncion', 'America/Asuncion'), ('America/Atikokan', 'America/Atikokan'), ('America/Atka', 'America/Atka'), ('America/Bahia', 'America/Bahia'), ('America/Bahia_Banderas', 'America/Bahia_Banderas'), ('America/Barbados', 'America/Barbados'), ('America/Belem', 'America/Belem'), ('America/Belize', 'America/Belize'), ('America/Blanc-Sablon', 'America/Blanc-Sablon'), ('America/Boa_Vista', 'America/Boa_Vista'), ('America/Bogota', 'America/Bogota'), ('America/Boise', 'America/Boise'), ('America/Buenos_Aires', 'America/Buenos_Aires'), ('America/Cambridge_Bay', 'America/Cambridge_Bay'), ('America/Campo_Grande', 'America/Campo_Grande'), ('America/Cancun', 'America/Cancun'), ('America/Caracas', 'America/Caracas'), ('America/Catamarca', 'America/Catamarca'), ('America/Cayenne', 'America/Cayenne'), ('America/Cayman', 'America/Cayman'), ('America/Chicago', 'America/Chicago'), ('America/Chihuahua', 'America/Chihuahua'), ('America/Ciudad_Juarez', 'America/Ciudad_Juarez'), ('America/Coral_Harbour', 'America/Coral_Harbour'), ('America/Cordoba', 'America/Cordoba'), ('America/Costa_Rica', 'America/Costa_Rica'), ('America/Creston', 'America/Creston'), ('America/Cuiaba', 'America/Cuiaba'), ('America/Curacao', 'America/Curacao'), ('America/Danmarkshavn', 'America/Danmarkshavn'), ('America/Dawson', 'America/Dawson'), ('America/Dawson_Creek', 'America/Dawson_Creek'), ('America/Denver', 'America/Denver'), ('America/Detroit', 'America/Detroit'), ('America/Dominica', 'America/Dominica'), ('America/Edmonton', 'America/Edmonton'), ('America/Eirunepe', 'America/Eirunepe'), ('America/El_Salvador', 'America/El_Salvador'), ('America/Ensenada', 'America/Ensenada'), ('America/Fort_Nelson', 'America/Fort_Nelson'), ('America/Fort_Wayne', 'America/Fort_Wayne'), ('America/Fortaleza', 'America/Fortaleza'), ('America/Glace_Bay', 'America/Glace_Bay'), ('America/Godthab', 'America/Godthab'), ('America/Goose_Bay', 'America/Goose_Bay'), ('America/Grand_Turk', 'America/Grand_Turk'), ('America/Grenada', 'America/Grenada'), ('America/Guadeloupe', 'America/Guadeloupe'), ('America/Guatemala', 'America/Guatemala'), ('America/Guayaquil', 'America/Guayaquil'), ('America/Guyana', 'America/Guyana'), ('America/Halifax', 'America/Halifax'), ('America/Havana', 'America/Havana'), ('America/Hermosillo', 'America/Hermosillo'), ('America/Indiana/Indianapolis', 'America/Indiana/Indianapolis'), ('America/Indiana/Knox', 'America/Indiana/Knox'), ('America/Indiana/Marengo', 'America/Indiana/Marengo'), ('America/Indiana/Petersburg', 'America/Indiana/Petersburg'), ('America/Indiana/Tell_City', 'America/Indiana/Tell_City'), ('America/Indiana/Vevay', 'America/Indiana/Vevay'), ('America/Indiana/Vincennes', 'America/Indiana/Vincennes'), ('America/Indiana/Winamac', 'America/Indiana/Winamac'), ('America/Indianapolis', 'America/Indianapolis'), ('America/Inuvik', 'America/Inuvik'), ('America/Iqaluit', 'America/Iqaluit'), ('America/Jamaica', 'America/Jamaica'), ('America/Jujuy', 'America/Jujuy'), ('America/Juneau', 'America/Juneau'), ('America/Kentucky/Louisville', 'America/Kentucky/Louisville'), ('America/Kentucky/Monticello', 'America/Kentucky/Monticello'), ('America/Knox_IN', 'America/Knox_IN'), ('America/Kralendijk', 'America/Kralendijk'), ('America/La_Paz', 'America/La_Paz'), ('America/Lima', 'America/Lima'), ('America/Los_Angeles', 'America/Los_Angeles'), ('America/Louisville', 'America/Louisville'), ('America/Lower_Princes', 'America/Lower_Princes'), ('America/Maceio', 'America/Maceio'), ('America/Managua', 'America/Managua'), ('America/Manaus', 'America/Manaus'), ('America/Marigot', 'America/Marigot'), ('America/Martinique', 'America/Martinique'), ('America/Matamoros', 'America/Matamoros'), ('America/Mazatlan', 'America/Mazatlan'), ('America/Mendoza', 'America/Mendoza'), ('America/Menominee', 'America/Menominee'), ('America/Merida', 'America/Merida'), ('America/Metlakatla', 'America/Metlakatla'), ('America/Mexico_City', 'America/Mexico_City'), ('America/Miquelon', 'America/Miquelon'), ('America/Moncton', 'America/Moncton'), ('America/Monterrey', 'America/Monterrey'), ('America/Montevideo', 'America/Montevideo'), ('America/Montreal', 'America/Montreal'), ('America/Montserrat', 'America/Montserrat'), ('America/Nassau', 'America/Nassau'), ('America/New_York', 'America/New_York'), ('America/Nipigon', 'America/Nipigon'), ('America/Nome', 'America/Nome'), ('America/Noronha', 'America/Noronha'), ('America/North_Dakota/Beulah', 'America/North_Dakota/Beulah'), ('America/North_Dakota/Center', 'America/North_Dakota/Center'), ('America/North_Dakota/New_Salem', 'America/North_Dakota/New_Salem'), ('America/Nuuk', 'America/Nuuk'), ('America/Ojinaga', 'America/Ojinaga'), ('America/Panama', 'America/Panama'), ('America/Pangnirtung', 'America/Pangnirtung'), ('America/Paramaribo', 'America/Paramaribo'), ('America/Phoenix', 'America/Phoenix'), ('America/Port-au-Prince', 'America/Port-au-Prince'), ('America/Port_of_Spain', 'America/Port_of_Spain'), ('America/Porto_Acre', 'America/Porto_Acre'), ('America/Porto_Velho', 'America/Porto_Velho'), ('America/Puerto_Rico', 'America/Puerto_Rico'), ('America/Punta_Arenas', 'America/Punta_Arenas'), ('America/Rainy_River', 'America/Rainy_River'), ('America/Rankin_Inlet', 'America/Rankin_Inlet'), ('America/Recife', 'America/Recife'), ('America/Regina', 'America/Regina'), ('America/Resolute', 'America/Resolute'), ('America/Rio_Branco', 'America/Rio_Branco'), ('America/Rosario', 'America/Rosario'), ('America/Santa_Isabel', 'America/Santa_Isabel'), ('America/Santarem', 'America/Santarem'), ('America/Santiago', 'America/Santiago'), ('America/Santo_Domingo', 'America/Santo_Domingo'), ('America/Sao_Paulo', 'America/Sao_Paulo'), ('America/Scoresbysund', 'America/Scoresbysund'), ('America/Shiprock', 'America/Shiprock'), ('America/Sitka', 'America/Sitka'), ('America/St_Barthelemy', 'America/St_Barthelemy'), ('America/St_Johns', 'America/St_Johns'), ('America/St_Kitts', 'America/St_Kitts'), ('America/St_Lucia', 'America/St_Lucia'), ('America/St_Thomas', 'America/St_Thomas'), ('America/St_Vincent', 'America/St_Vincent'), ('America/Swift_Current', 'America/Swift_Current'), ('America/Tegucigalpa', 'America/Tegucigalpa'), ('America/Thule', 'America/Thule'), ('America/Thunder_Bay', 'America/Thunder_Bay'), ('America/Tijuana', 'America/Tijuana'), ('America/Toronto', 'America/Toronto'), ('America/Tortola', 'America/Tortola'), ('America/Vancouver', 'America/Vancouver'), ('America/Virgin', 'America/Virgin'), ('America/Whitehorse', 'America/Whitehorse'), ('America/Winnipeg', 'America/Winnipeg'), ('America/Yakutat', 'America/Yakutat'), ('America/Yellowknife', 'America/Yellowknife'), ('Antarctica/Casey', 'Antarctica/Casey'), ('Antarctica/Davis', 'Antarctica/Davis'), ('Antarctica/DumontDUrville', 'Antarctica/DumontDUrville'), ('Antarctica/Macquarie', 'Antarctica/Macquarie'), ('Antarctica/Mawson', 'Antarctica/Mawson'), ('Antarctica/McMurdo', 'Antarctica/McMurdo'), ('Antarctica/Palmer', 'Antarctica/Palmer'), ('Antarctica/Rothera', 'Antarctica/Rothera'), ('Antarctica/South_Pole', 'Antarctica/South_Pole'), ('Antarctica/Syowa', 'Antarctica/Syowa'), ('Antarctica/Troll', 'Antarctica/Troll'), ('Antarctica/Vostok', 'Antarctica/Vostok'), ('Arctic/Longyearbyen', 'Arctic/Longyearbyen'), ('Asia/Aden', 'Asia/Aden'), ('Asia/Almaty', 'Asia/Almaty'), ('Asia/Amman', 'Asia/Amman'), ('Asia/Anadyr', 'Asia/Anadyr'), ('Asia/Aqtau', 'Asia/Aqtau'), ('Asia/Aqtobe', 'Asia/Aqtobe'), ('Asia/Ashgabat', 'Asia/Ashgabat'), ('Asia/Ashkhabad', 'Asia/Ashkhabad'), ('Asia/Atyrau', 'Asia/Atyrau'), ('Asia/Baghdad', 'Asia/Baghdad'), ('Asia/Bahrain', 'Asia/Bahrain'), ('Asia/Baku', 'Asia/Baku'), ('Asia/Bangkok', 'Asia/Bangkok'), ('Asia/Barnaul', 'Asia/Barnaul'), ('Asia/Beirut', 'Asia/Beirut'), ('Asia/Bishkek', 'Asia/Bishkek'), ('Asia/Brunei', 'Asia/Brunei'), ('Asia/Calcutta', 'Asia/Calcutta'), ('Asia/Chita', 'Asia/Chita'), ('Asia/Choibalsan', 'Asia/Choibalsan'), ('Asia/Chongqing', 'Asia/Chongqing'), ('Asia/Chungking', 'Asia/Chungking'), ('Asia/Colombo', 'Asia/Colombo'), ('Asia/Dacca', 'Asia/Dacca'), ('Asia/Damascus', 'Asia/Damascus'), ('Asia/Dhaka', 'Asia/Dhaka'), ('Asia/Dili', 'Asia/Dili'), ('Asia/Dubai', 'Asia/Dubai'), ('Asia/Dushanbe', 'Asia/Dushanbe'), ('Asia/Famagusta', 'Asia/Famagusta'), ('Asia/Gaza', 'Asia/Gaza'), ('Asia/Harbin', 'Asia/Harbin'), ('Asia/Hebron', 'Asia/Hebron'), ('Asia/Ho_Chi_Minh', 'Asia/Ho_Chi_Minh'), ('Asia/Hong_Kong', 'Asia/Hong_Kong'), ('Asia/Hovd', 'Asia/Hovd'), ('Asia/Irkutsk', 'Asia/Irkutsk'), ('Asia/Istanbul', 'Asia/Istanbul'), ('Asia/Jakarta', 'Asia/Jakarta'), ('Asia/Jayapura', 'Asia/Jayapura'), ('Asia/Jerusalem', 'Asia/Jerusalem'), ('Asia/Kabul', 'Asia/Kabul'), ('Asia/Kamchatka', 'Asia/Kamchatka'), ('Asia/Karachi', 'Asia/Karachi'), ('Asia/Kashgar', 'Asia/Kashgar'), ('Asia/Kathmandu', 'Asia/Kathmandu'), ('Asia/Katmandu', 'Asia/Katmandu'), ('Asia/Khandyga', 'Asia/Khandyga'), ('Asia/Kolkata', 'Asia/Kolkata'), ('Asia/Krasnoyarsk', 'Asia/Krasnoyarsk'), ('Asia/Kuala_Lumpur', 'Asia/Kuala_Lumpur'), ('Asia/Kuching', 'Asia/Kuching'), ('Asia/Kuwait', 'Asia/Kuwait'), ('Asia/Macao', 'Asia/Macao'), ('Asia/Macau', 'Asia/Macau'), ('Asia/Magadan', 'Asia/Magadan'), ('Asia/Makassar', 'Asia/Makassar'), ('Asia/Manila', 'Asia/Manila'), ('Asia/Muscat', 'Asia/Muscat'), ('Asia/Nicosia', 'Asia/Nicosia'), ('Asia/Novokuznetsk', 'Asia/Novokuznetsk'), ('Asia/Novosibirsk', 'Asia/Novosibirsk'), ('Asia/Omsk', 'Asia/Omsk'), ('Asia/Oral', 'Asia/Oral'), ('Asia/Phnom_Penh', 'Asia/Phnom_Penh'), ('Asia/Pontianak', 'Asia/Pontianak'), ('Asia/Pyongyang', 'Asia/Pyongyang'), ('Asia/Qatar', 'Asia/Qatar'), ('Asia/Qostanay', 'Asia/Qostanay'), ('Asia/Qyzylorda', 'Asia/Qyzylorda'), ('Asia/Rangoon', 'Asia/Rangoon'), ('Asia/Riyadh', 'Asia/Riyadh'), ('Asia/Saigon', 'Asia/Saigon'), ('Asia/Sakhalin', 'Asia/Sakhalin'), ('Asia/Samarkand', 'Asia/Samarkand'), ('Asia/Seoul', 'Asia/Seoul'), ('Asia/Shanghai', 'Asia/Shanghai'), ('Asia/Singapore', 'Asia/Singapore'), ('Asia/Srednekolymsk', 'Asia/Srednekolymsk'), ('Asia/Taipei', 'Asia/Taipei'), ('Asia/Tashkent', 'Asia/Tashkent'), ('Asia/Tbilisi', 'Asia/Tbilisi'), ('Asia/Tehran', 'Asia/Tehran'), ('Asia/Tel_Aviv', 'Asia/Tel_Aviv'), ('Asia/Thimbu', 'Asia/Thimbu'), ('Asia/Thimphu', 'Asia/Thimphu'), ('Asia/Tokyo', 'Asia/Tokyo'), ('Asia/Tomsk', 'Asia/Tomsk'), ('Asia/Ujung_Pandang', 'Asia/Ujung_Pandang'), ('Asia/Ulaanbaatar', 'Asia/Ulaanbaatar'), ('Asia/Ulan_Bator', 'Asia/Ulan_Bator'), ('Asia/Urumqi', 'Asia/Urumqi'), ('Asia/Ust-Nera', 'Asia/Ust-Nera'), ('Asia/Vientiane', 'Asia/Vientiane'), ('Asia/Vladivostok', 'Asia/Vladivostok'), ('Asia/Yakutsk', 'Asia/Yakutsk'), ('Asia/Yangon', 'Asia/Yangon'), ('Asia/Yekaterinburg', 'Asia/Yekaterinburg'), ('Asia/Yerevan', 'Asia/Yerevan'), ('Atlantic/Azores', 'Atlantic/Azores'), ('Atlantic/Bermuda', 'Atlantic/Bermuda'), ('Atlantic/Canary', 'Atlantic/Canary'), ('Atlantic/Cape_Verde', 'Atlantic/Cape_Verde'), ('Atlantic/Faeroe', 'Atlantic/Faeroe'), ('Atlantic/Faroe', 'Atlantic/Faroe'), ('Atlantic/Jan_Mayen', 'Atlantic/Jan_Mayen'), ('Atlantic/Madeira', 'Atlantic/Madeira'), ('Atlantic/Reykjavik', 'Atlantic/Reykjavik'), ('Atlantic/South_Georgia', 'Atlantic/South_Georgia'), ('Atlantic/St_Helena', 'Atlantic/St_Helena'), ('Atlantic/Stanley', 'Atlantic/Stanley'), ('Australia/ACT', 'Australia/ACT'), ('Australia/Adelaide', 'Australia/Adelaide'), ('Australia/Brisbane', 'Australia/Brisbane'), ('Australia/Broken_Hill', 'Australia/Broken_Hill'), ('Australia/Canberra', 'Australia/Canberra'), ('Australia/Currie', 'Australia/Currie'), ('Australia/Darwin', 'Australia/Darwin'), ('Australia/Eucla', 'Australia/Eucla'), ('Australia/Hobart', 'Australia/Hobart'), ('Australia/LHI', 'Australia/LHI'), ('Australia/Lindeman', 'Australia/Lindeman'), ('Australia/Lord_Howe', 'Australia/Lord_Howe'), ('Australia/Melbourne', 'Australia/Melbourne'), ('Australia/NSW', 'Australia/NSW'), ('Australia/North', 'Australia/North'), ('Australia/Perth', 'Australia/Perth'), ('Australia/Queensland', 'Australia/Queensland'), ('Australia/South', 'Australia/South'), ('Australia/Sydney', 'Australia/Sydney'), ('Australia/Tasmania', 'Australia/Tasmania'), ('Australia/Victoria', 'Australia/Victoria'), ('Australia/West', 'Australia/West'), ('Australia/Yancowinna', 'Australia/Yancowinna'), ('Brazil/Acre', 'Brazil/Acre'), ('Brazil/DeNoronha', 'Brazil/DeNoronha'), ('Brazil/East', 'Brazil/East'), ('Brazil/West', 'Brazil/West'), ('CET', 'CET'), ('CST6CDT', 'CST6CDT'), ('Canada/Atlantic', 'Canada/Atlantic'), ('Canada/Central', 'Canada/Central'), ('Canada/Eastern', 'Canada/Eastern'), ('Canada/Mountain', 'Canada/Mountain'), ('Canada/Newfoundland', 'Canada/Newfoundland'), ('Canada/Pacific', 'Canada/Pacific'), ('Canada/Saskatchewan', 'Canada/Saskatchewan'), ('Canada/Yukon', 'Canada/Yukon'), ('Chile/Continental', 'Chile/Continental'), ('Chile/EasterIsland', 'Chile/EasterIsland'), ('Cuba', 'Cuba'), ('EET', 'EET'), ('EST', 'EST'), ('EST5EDT', 'EST5EDT'), ('Egypt', 'Egypt'), ('Eire', 'Eire'), ('Etc/GMT', 'Etc/GMT'), ('Etc/GMT+0', 'Etc/GMT+0'), ('Etc/GMT+1', 'Etc/GMT+1'), ('Etc/GMT+10', 'Etc/GMT+10'), ('Etc/GMT+11', 'Etc/GMT+11'), ('Etc/GMT+12', 'Etc/GMT+12'), ('Etc/GMT+2', 'Etc/GMT+2'), ('Etc/GMT+3', 'Etc/GMT+3'), ('Etc/GMT+4', 'Etc/GMT+4'), ('Etc/GMT+5', 'Etc/GMT+5'), ('Etc/GMT+6', 'Etc/GMT+6'), ('Etc/GMT+7', 'Etc/GMT+7'), ('Etc/GMT+8', 'Etc/GMT+8'), ('Etc/GMT+9', 'Etc/GMT+9'), ('Etc/GMT-0', 'Etc/GMT-0'), ('Etc/GMT-1', 'Etc/GMT-1'), ('Etc/GMT-10', 'Etc/GMT-10'), ('Etc/GMT-11', 'Etc/GMT-11'), ('Etc/GMT-12', 'Etc/GMT-12'), ('Etc/GMT-13', 'Etc/GMT-13'), ('Etc/GMT-14', 'Etc/GMT-14'), ('Etc/GMT-2', 'Etc/GMT-2'), ('Etc/GMT-3', 'Etc/GMT-3'), ('Etc/GMT-4', 'Etc/GMT-4'), ('Etc/GMT-5', 'Etc/GMT-5'), ('Etc/GMT-6', 'Etc/GMT-6'), ('Etc/GMT-7', 'Etc/GMT-7'), ('Etc/GMT-8', 'Etc/GMT-8'), ('Etc/GMT-9', 'Etc/GMT-9'), ('Etc/GMT0', 'Etc/GMT0'), ('Etc/Greenwich', 'Etc/Greenwich'), ('Etc/UCT', 'Etc/UCT'), ('Etc/UTC', 'Etc/UTC'), ('Etc/Universal', 'Etc/Universal'), ('Etc/Zulu', 'Etc/Zulu'), ('Europe/Amsterdam', 'Europe/Amsterdam'), ('Europe/Andorra', 'Europe/Andorra'), ('Europe/Astrakhan', 'Europe/Astrakhan'), ('Europe/Athens', 'Europe/Athens'), ('Europe/Belfast', 'Europe/Belfast'), ('Europe/Belgrade', 'Europe/Belgrade'), ('Europe/Berlin', 'Europe/Berlin'), ('Europe/Bratislava', 'Europe/Bratislava'), ('Europe/Brussels', 'Europe/Brussels'), ('Europe/Bucharest', 'Europe/Bucharest'), ('Europe/Budapest', 'Europe/Budapest'), ('Europe/Busingen', 'Europe/Busingen'), ('Europe/Chisinau', 'Europe/Chisinau'), ('Europe/Copenhagen', 'Europe/Copenhagen'), ('Europe/Dublin', 'Europe/Dublin'), ('Europe/Gibraltar', 'Europe/Gibraltar'), ('Europe/Guernsey', 'Europe/Guernsey'), ('Europe/Helsinki', 'Europe/Helsinki'), ('Europe/Isle_of_Man', 'Europe/Isle_of_Man'), ('Europe/Istanbul', 'Europe/Istanbul'), ('Europe/Jersey', 'Europe/Jersey'), ('Europe/Kaliningrad', 'Europe/Kaliningrad'), ('Europe/Kiev', 'Europe/Kiev'), ('Europe/Kirov', 'Europe/Kirov'), ('Europe/Kyiv', 'Europe/Kyiv'), ('Europe/Lisbon', 'Europe/Lisbon'), ('Europe/Ljubljana', 'Europe/Ljubljana'), ('Europe/London', 'Europe/London'), ('Europe/Luxembourg', 'Europe/Luxembourg'), ('Europe/Madrid', 'Europe/Madrid'), ('Europe/Malta', 'Europe/Malta'), ('Europe/Mariehamn', 'Europe/Mariehamn'), ('Europe/Minsk', 'Europe/Minsk'), ('Europe/Monaco', 'Europe/Monaco'), ('Europe/Moscow', 'Europe/Moscow'), ('Europe/Nicosia', 'Europe/Nicosia'), ('Europe/Oslo', 'Europe/Oslo'), ('Europe/Paris', 'Europe/Paris'), ('Europe/Podgorica', 'Europe/Podgorica'), ('Europe/Prague', 'Europe/Prague'), ('Europe/Riga', 'Europe/Riga'), ('Europe/Rome', 'Europe/Rome'), ('Europe/Samara', 'Europe/Samara'), ('Europe/San_Marino', 'Europe/San_Marino'), ('Europe/Sarajevo', 'Europe/Sarajevo'), ('Europe/Saratov', 'Europe/Saratov'), ('Europe/Simferopol', 'Europe/Simferopol'), ('Europe/Skopje', 'Europe/Skopje'), ('Europe/Sofia', 'Europe/Sofia'), ('Europe/Stockholm', 'Europe/Stockholm'), ('Europe/Tallinn', 'Europe/Tallinn'), ('Europe/Tirane', 'Europe/Tirane'), ('Europe/Tiraspol', 'Europe/Tiraspol'), ('Europe/Ulyanovsk', 'Europe/Ulyanovsk'), ('Europe/Uzhgorod', 'Europe/Uzhgorod'), ('Europe/Vaduz', 'Europe/Vaduz'), ('Europe/Vatican', 'Europe/Vatican'), ('Europe/Vienna', 'Europe/Vienna'), ('Europe/Vilnius', 'Europe/Vilnius'), ('Europe/Volgograd', 'Europe/Volgograd'), ('Europe/Warsaw', 'Europe/Warsaw'), ('Europe/Zagreb', 'Europe/Zagreb'), ('Europe/Zaporozhye', 'Europe/Zaporozhye'), ('Europe/Zurich', 'Europe/Zurich'), ('Factory', 'Factory'), ('GB', 'GB'), ('GB-Eire', 'GB-Eire'), ('GMT', 'GMT'), ('GMT+0', 'GMT+0'), ('GMT-0', 'GMT-0'), ('GMT0', 'GMT0'), ('Greenwich', 'Greenwich'), ('HST', 'HST'), ('Hongkong', 'Hongkong'), ('Iceland', 'Iceland'), ('Indian/Antananarivo', 'Indian/Antananarivo'), ('Indian/Chagos', 'Indian/Chagos'), ('Indian/Christmas', 'Indian/Christmas'), ('Indian/Cocos', 'Indian/Cocos'), ('Indian/Comoro', 'Indian/Comoro'), ('Indian/Kerguelen', 'Indian/Kerguelen'), ('Indian/Mahe', 'Indian/Mahe'), ('Indian/Maldives', 'Indian/Maldives'), ('Indian/Mauritius', 'Indian/Mauritius'), ('Indian/Mayotte', 'Indian/Mayotte'), ('Indian/Reunion', 'Indian/Reunion'), ('Iran', 'Iran'), ('Israel', 'Israel'), ('Jamaica', 'Jamaica'), ('Japan', 'Japan'), ('Kwajalein', 'Kwajalein'), ('Libya', 'Libya'), ('MET', 'MET'), ('MST', 'MST'), ('MST7MDT', 'MST7MDT'), ('Mexico/BajaNorte', 'Mexico/BajaNorte'), ('Mexico/BajaSur', 'Mexico/BajaSur'), ('Mexico/General', 'Mexico/General'), ('NZ', 'NZ'), ('NZ-CHAT', 'NZ-CHAT'), ('Navajo', 'Navajo'), ('PRC', 'PRC'), ('PST8PDT', 'PST8PDT'), ('Pacific/Apia', 'Pacific/Apia'), ('Pacific/Auckland', 'Pacific/Auckland'), ('Pacific/Bougainville', 'Pacific/Bougainville'), ('Pacific/Chatham', 'Pacific/Chatham'), ('Pacific/Chuuk', 'Pacific/Chuuk'), ('Pacific/Easter', 'Pacific/Easter'), ('Pacific/Efate', 'Pacific/Efate'), ('Pacific/Enderbury', 'Pacific/Enderbury'), ('Pacific/Fakaofo', 'Pacific/Fakaofo'), ('Pacific/Fiji', 'Pacific/Fiji'), ('Pacific/Funafuti', 'Pacific/Funafuti'), ('Pacific/Galapagos', 'Pacific/Galapagos'), ('Pacific/Gambier', 'Pacific/Gambier'), ('Pacific/Guadalcanal', 'Pacific/Guadalcanal'), ('Pacific/Guam', 'Pacific/Guam'), ('Pacific/Honolulu', 'Pacific/Honolulu'), ('Pacific/Johnston', 'Pacific/Johnston'), ('Pacific/Kanton', 'Pacific/Kanton'), ('Pacific/Kiritimati', 'Pacific/Kiritimati'), ('Pacific/Kosrae', 'Pacific/Kosrae'), ('Pacific/Kwajalein', 'Pacific/Kwajalein'), ('Pacific/Majuro', 'Pacific/Majuro'), ('Pacific/Marquesas', 'Pacific/Marquesas'), ('Pacific/Midway', 'Pacific/Midway'), ('Pacific/Nauru', 'Pacific/Nauru'), ('Pacific/Niue', 'Pacific/Niue'), ('Pacific/Norfolk', 'Pacific/Norfolk'), ('Pacific/Noumea', 'Pacific/Noumea'), ('Pacific/Pago_Pago', 'Pacific/Pago_Pago'), ('Pacific/Palau', 'Pacific/Palau'), ('Pacific/Pitcairn', 'Pacific/Pitcairn'), ('Pacific/Pohnpei', 'Pacific/Pohnpei'), ('Pacific/Ponape', 'Pacific/Ponape'), ('Pacific/Port_Moresby', 'Pacific/Port_Moresby'), ('Pacific/Rarotonga', 'Pacific/Rarotonga'), ('Pacific/Saipan', 'Pacific/Saipan'), ('Pacific/Samoa', 'Pacific/Samoa'), ('Pacific/Tahiti', 'Pacific/Tahiti'), ('Pacific/Tarawa', 'Pacific/Tarawa'), ('Pacific/Tongatapu', 'Pacific/Tongatapu'), ('Pacific/Truk', 'Pacific/Truk'), ('Pacific/Wake', 'Pacific/Wake'), ('Pacific/Wallis', 'Pacific/Wallis'), ('Pacific/Yap', 'Pacific/Yap'), ('Poland', 'Poland'), ('Portugal', 'Portugal'), ('ROC', 'ROC'), ('ROK', 'ROK'), ('Singapore', 'Singapore'), ('Turkey', 'Turkey'), ('UCT', 'UCT'), ('US/Alaska', 'US/Alaska'), ('US/Aleutian', 'US/Aleutian'), ('US/Arizona', 'US/Arizona'), ('US/Central', 'US/Central'), ('US/East-Indiana', 'US/East-Indiana'), ('US/Eastern', 'US/Eastern'), ('US/Hawaii', 'US/Hawaii'), ('US/Indiana-Starke', 'US/Indiana-Starke'), ('US/Michigan', 'US/Michigan'), ('US/Mountain', 'US/Mountain'), ('US/Pacific', 'US/Pacific'), ('US/Samoa', 'US/Samoa'), ('UTC', 'UTC'), ('Universal', 'Universal'), ('W-SU', 'W-SU'), ('WET', 'WET'), ('Zulu', 'Zulu')], default='CET', max_length=50, null=True), + ), + ] diff --git a/tournaments/models/club.py b/tournaments/models/club.py index 1a13cfe..0d62c18 100644 --- a/tournaments/models/club.py +++ b/tournaments/models/club.py @@ -17,7 +17,7 @@ class Club(models.Model): latitude = models.FloatField(null=True, blank=True) longitude = models.FloatField(null=True, blank=True) timezone = models.CharField( - max_length=50, + max_length=50, null=True, blank=True, choices=[(tz, tz) for tz in sorted(available_timezones())], default='CET' ) diff --git a/tournaments/models/match.py b/tournaments/models/match.py index 6a0d954..d2bb397 100644 --- a/tournaments/models/match.py +++ b/tournaments/models/match.py @@ -79,16 +79,16 @@ class Match(models.Model): def player_names(self): return map(lambda ts: ts.player_names(), self.team_scores.all()) + def local_start_date(self): + timezone = self.tournament().timezone() + return self.start_date.astimezone(timezone) + def formatted_start_date(self): if self.start_date: - timezone = self.tournament().timezone() - local_start = self.start_date.astimezone(timezone) - # timezoned_datetime = timezone.localtime(self.start_date) + local_start = self.local_start_date() return formats.date_format(local_start, format='H:i') - # return formats.date_format(self.start_date, format='H:i') else: return '' - # return str(self.start_date) #.strftime("%H:%M") def time_indication(self): if self.end_date: @@ -100,8 +100,10 @@ class Match(models.Model): if self.started(): return self.formatted_duration() else: - timezoned_datetime = timezone.localtime(self.start_date) - return formats.date_format(timezoned_datetime, format='l H:i') + # timezoned_datetime = timezone.localtime(self.start_date) + timezone = self.tournament().timezone() + local_start = self.start_date.astimezone(timezone) + return formats.date_format(local_start, format='l H:i') else: return 'À venir...' @@ -188,14 +190,14 @@ class Match(models.Model): def live_match(self): title = self.name if self.name else self.backup_name() date = self.formatted_start_date() - duration = self.time_indication() + time_indication = self.time_indication() court = self.court_name(self.court_index) group_stage_name = None if self.group_stage: group_stage_name = self.group_stage.display_name() ended = self.end_date is not None - livematch = LiveMatch(title, date, duration, court, self.started(), ended, group_stage_name) + livematch = LiveMatch(title, date, time_indication, court, self.started(), ended, group_stage_name) for team_score in self.sorted_team_scores(): if team_score.team_registration: @@ -257,11 +259,11 @@ class Team: } class LiveMatch: - def __init__(self, title, date, duration, court, started, ended, group_stage_name): + def __init__(self, title, date, time_indication, court, started, ended, group_stage_name): self.title = title self.date = date self.teams = [] - self.duration = duration + self.time_indication = time_indication self.court = court self.started = started self.ended = ended @@ -278,7 +280,7 @@ class LiveMatch: "title": self.title, "date": self.date, "teams": [team.to_dict() for team in self.teams], - "duration": self.duration, + "time_indication": self.time_indication, "court": self.court, "started": self.started, "ended": self.ended, @@ -286,7 +288,7 @@ class LiveMatch: "group_stage_name": self.group_stage_name, } - def show_duration(self): + def show_time_indication(self): for team in self.teams: if team.walk_out and len(team.scores) == 0: return False diff --git a/tournaments/models/tournament.py b/tournaments/models/tournament.py index 9e47813..c2fa385 100644 --- a/tournaments/models/tournament.py +++ b/tournaments/models/tournament.py @@ -138,6 +138,13 @@ class Tournament(models.Model): tz = self.event.club.timezone return ZoneInfo(tz) + def local_start_date(self): + timezone = self.timezone() + return self.start_date.astimezone(timezone) + + def local_start_date_formatted(self): + return formats.date_format(self.local_start_date(), format='j F Y H:i') + def level(self): if self.federal_level_category == 0: return "Anim." @@ -240,7 +247,7 @@ class Tournament(models.Model): names = team_registration.team_names() stage = next_match.summon_stage_name() weight = team_registration.weight - summon = TeamSummon(names, next_match.start_date, weight, stage, next_match.court_name(next_match.court_index), team_registration.logo) + summon = TeamSummon(names, next_match.local_start_date(), weight, stage, next_match.court_name(next_match.court_index), team_registration.logo) summons.append(summon) summons.sort(key=lambda s: (s.date is None, s.date or datetime.min)) @@ -849,8 +856,7 @@ class TeamSummon: def formatted_date(self): if self.date: - timezoned_datetime = timezone.localtime(self.date) - return formats.date_format(timezoned_datetime, format='l H:i') + return formats.date_format(self.date, format='l H:i') else: return None diff --git a/tournaments/templates/tournaments/broadcast/broadcasted_match.html b/tournaments/templates/tournaments/broadcast/broadcasted_match.html index 66758cc..295b181 100644 --- a/tournaments/templates/tournaments/broadcast/broadcasted_match.html +++ b/tournaments/templates/tournaments/broadcast/broadcasted_match.html @@ -51,7 +51,7 @@
- +
diff --git a/tournaments/templates/tournaments/match_cell.html b/tournaments/templates/tournaments/match_cell.html index aea7beb..984bd81 100644 --- a/tournaments/templates/tournaments/match_cell.html +++ b/tournaments/templates/tournaments/match_cell.html @@ -42,8 +42,8 @@
-
{{ summon.date|date:'l H:i' }}
+
{{ summon.formatted_date }}
{{ summon.court }}
{{ summon.stage }}
diff --git a/tournaments/templates/tournaments/tournament_info.html b/tournaments/templates/tournaments/tournament_info.html index 319dbda..8ab6c16 100644 --- a/tournaments/templates/tournaments/tournament_info.html +++ b/tournaments/templates/tournaments/tournament_info.html @@ -7,6 +7,7 @@ {% block content %} {% load static %} +{% load tz %} {% include 'tournaments/navigation_tournament.html' %} @@ -17,7 +18,7 @@

-

{{ tournament.start_date }}
+
{{ tournament.local_start_date_formatted }}
{{ tournament.day_duration_formatted }}
{{ tournament.court_count }} terrains

From 6240c4d678e840b1140796bba2276000386c7a84 Mon Sep 17 00:00:00 2001 From: Laurent Date: Fri, 25 Oct 2024 09:27:48 +0200 Subject: [PATCH 6/6] Fix tournament mistakenly appearing in progress --- tournaments/models/tournament.py | 62 ++++++++++++++++++++------------ tournaments/views.py | 11 ++---- 2 files changed, 42 insertions(+), 31 deletions(-) diff --git a/tournaments/models/tournament.py b/tournaments/models/tournament.py index c2fa385..1acea4e 100644 --- a/tournaments/models/tournament.py +++ b/tournaments/models/tournament.py @@ -232,6 +232,7 @@ class Tournament(models.Model): def team_summons(self): summons = [] + print('>>> team_summons') if self.supposedly_in_progress() and self.end_date is None: for team in self.teams(False): names = team.names @@ -275,7 +276,7 @@ class Tournament(models.Model): return rankings def teams(self, includeWaitingList): - print("Starting teams method") + # print("Starting teams method") bracket_teams = [] group_stage_teams = [] waiting_teams = [] @@ -284,10 +285,10 @@ class Tournament(models.Model): wildcard_group_stage = [] complete_teams = [] closed_registration_date = self.closed_registration_date - print(f"Closed registration date: {closed_registration_date}") + # print(f"Closed registration date: {closed_registration_date}") for team_registration in self.teamregistration_set.all(): - print(f"Processing team registration: {team_registration}") + # print(f"Processing team registration: {team_registration}") is_valid = False if closed_registration_date is not None and team_registration.registration_date is not None and team_registration.registration_date <= closed_registration_date: is_valid = True @@ -295,7 +296,7 @@ class Tournament(models.Model): is_valid = True if team_registration.registration_date is None: is_valid = True - print(f"Is valid: {is_valid}") + # print(f"Is valid: {is_valid}") if team_registration.walk_out is False: names = team_registration.team_names() @@ -303,14 +304,14 @@ class Tournament(models.Model): initial_weight = team_registration.initial_weight() date = team_registration.call_date team = TeamList(names, weight, date, initial_weight, team_registration.wild_card_bracket, team_registration.wild_card_group_stage, team_registration.logo) - print(f"Created team: {team}") + # print(f"Created team: {team}") if team_registration.group_stage_position is not None: team.set_stage("Poule") elif team_registration.bracket_position is not None: team.set_stage("Tableau") else: team.set_stage("Attente") - print(f"Team stage: {team.stage}") + # print(f"Team stage: {team.stage}") teams.append(team) if team_registration.wild_card_bracket: @@ -322,11 +323,11 @@ class Tournament(models.Model): else: waiting_teams.append(team) - print(f"Total teams: {len(teams)}") - print(f"Wildcard bracket: {len(wildcard_bracket)}") - print(f"Wildcard group stage: {len(wildcard_group_stage)}") - print(f"Complete teams: {len(complete_teams)}") - print(f"Waiting teams: {len(waiting_teams)}") + # print(f"Total teams: {len(teams)}") + # print(f"Wildcard bracket: {len(wildcard_bracket)}") + # print(f"Wildcard group stage: {len(wildcard_group_stage)}") + # print(f"Complete teams: {len(complete_teams)}") + # print(f"Waiting teams: {len(waiting_teams)}") if len(teams) < self.team_count: teams.sort(key=lambda s: (s.initial_weight, s.date)) @@ -339,8 +340,8 @@ class Tournament(models.Model): group_stage_members_count = 0 if seeds_count < 0: seeds_count = 0 - print(f"Seeds count: {seeds_count}") - print(f"Group stage members count: {group_stage_members_count}") + # print(f"Seeds count: {seeds_count}") + # print(f"Group stage members count: {group_stage_members_count}") if self.team_sorting == TeamSortingType.INSCRIPTION_DATE: complete_teams.sort(key=lambda s: (s.date is None, s.date or datetime.min, s.initial_weight)) @@ -349,25 +350,25 @@ class Tournament(models.Model): selected_teams = complete_teams[:self.team_count] selected_teams.sort(key=lambda s: s.initial_weight) - print(f"Selected teams: {len(selected_teams)}") + # print(f"Selected teams: {len(selected_teams)}") if seeds_count > 0: bracket_teams = selected_teams[:seeds_count] + wildcard_bracket else: bracket_teams = [] - print(f"Bracket teams: {len(bracket_teams)}") + # print(f"Bracket teams: {len(bracket_teams)}") if group_stage_members_count: group_stage_end = seeds_count + group_stage_members_count group_stage_teams = selected_teams[seeds_count:group_stage_end] + wildcard_group_stage else: group_stage_teams = [] - print(f"Group stage teams: {len(group_stage_teams)}") + # print(f"Group stage teams: {len(group_stage_teams)}") waiting_list_count = len(teams) - self.team_count if waiting_list_count < 0: waiting_list_count = 0 - print(f"Waiting list count: {waiting_list_count}") + # print(f"Waiting list count: {waiting_list_count}") if waiting_list_count > 0 or len(waiting_teams) > 0: if waiting_list_count > 0: @@ -378,7 +379,7 @@ class Tournament(models.Model): waiting_teams.sort(key=lambda s: (s.initial_weight, s.date)) else: waiting_teams = [] - print(f"Final waiting teams: {len(waiting_teams)}") + # print(f"Final waiting teams: {len(waiting_teams)}") bracket_teams.sort(key=lambda s: s.weight) group_stage_teams.sort(key=lambda s: s.weight) @@ -396,10 +397,10 @@ class Tournament(models.Model): if includeWaitingList is True: final_teams = bracket_teams + group_stage_teams + waiting_teams - print(f"Final teams with waiting list: {len(final_teams)}") + # print(f"Final teams with waiting list: {len(final_teams)}") else: final_teams = bracket_teams + group_stage_teams - print(f"Final teams without waiting list: {len(final_teams)}") + # print(f"Final teams without waiting list: {len(final_teams)}") return final_teams @@ -807,8 +808,23 @@ class Tournament(models.Model): return date.replace(hour=8, minute=0, second=0, microsecond=0, tzinfo=date.tzinfo) def supposedly_in_progress(self): - end = self.start_date + timedelta(days=self.day_duration + 1) - return self.start_date.replace(hour=0, minute=0) <= timezone.now() <= end + # end = self.start_date + timedelta(days=self.day_duration + 1) + # return self.start_date.replace(hour=0, minute=0) <= timezone.now() <= end + + timezoned_datetime = timezone.localtime(self.start_date) + end = timezoned_datetime + timedelta(days=self.day_duration + 1) + now = timezone.now() + + start = timezoned_datetime.replace(hour=0, minute=0) + + print(f"timezoned_datetime: {timezoned_datetime}") + print(f"tournament end date: {end}") + print(f"current time: {now}") + print(f"tournament start: {start}") + print(f"start <= now <= end: {start <= now <= end}") + + return start <= now <= end + def display_points_earned(self): return self.federal_level_category != FederalLevelCategory.UNLISTED and self.hide_points_earned is False @@ -858,7 +874,7 @@ class TeamSummon: if self.date: return formats.date_format(self.date, format='l H:i') else: - return None + return '' def to_dict(self): return { diff --git a/tournaments/views.py b/tournaments/views.py index 367b32c..e30e420 100644 --- a/tournaments/views.py +++ b/tournaments/views.py @@ -79,8 +79,8 @@ def live_tournaments(club_id): return [t for t in tournaments if t.display_tournament() and t.supposedly_in_progress()] def future_tournaments(club_id): - tomorrow = date.today() + timedelta(days=1) - tournaments = tournaments_query(Q(end_date__isnull=True, start_date__gt=tomorrow), club_id, True) + tomorrow = datetime.now().date() + timedelta(days=1) + tournaments = tournaments_query(Q(end_date__isnull=True, start_date__gte=tomorrow), club_id, True) return [t for t in tournaments if t.display_tournament()] def tournament_info(request, tournament_id): @@ -147,13 +147,8 @@ def tournament(request, tournament_id): rounds = list(tournament.round_set.filter(group_stage_loser_bracket=True)) rounds.extend(bracket_rounds) - #group_stages = tournament.groupstage_set.order_by('index') group_stages = sorted(tournament.get_computed_group_stage(), key=lambda s: (s.step, s.index)) - #print(len(match_groups)) - #print(len(rounds)) - #print(len(group_stages)) - if tournament.display_matches() or tournament.display_group_stages(): return render(request, 'tournaments/matches.html', { 'tournament': tournament, @@ -430,7 +425,7 @@ def simple_form_view(request): return HttpResponse(f"Hello, {name}!") # Simple response with name else: form = SimpleForm() - print(request.method) + # print(request.method) # If this is a GET request, we display an empty form return render(request, 'tournaments/admin/mail_test.html', {'form': form})