Skip to content

Commit 2926d84

Browse files
Timezones fix
1 parent 564cc76 commit 2926d84

File tree

6 files changed

+117
-22
lines changed

6 files changed

+117
-22
lines changed

app.py

Lines changed: 96 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import os
22
from datetime import datetime, timedelta
3+
import pytz
4+
import time
35
from flask import Flask, render_template, request, redirect, url_for, flash, jsonify, send_from_directory
46
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
57
from flask_sqlalchemy import SQLAlchemy
@@ -52,6 +54,75 @@
5254
# Initialize extensions
5355
db.init_app(app)
5456

57+
# Configure local timezone detection
58+
def get_local_timezone():
59+
"""Detect the local system timezone"""
60+
# Try environment variable first (Docker/container support)
61+
tz_env = os.environ.get('TZ')
62+
if tz_env:
63+
try:
64+
return pytz.timezone(tz_env)
65+
except pytz.UnknownTimeZoneError:
66+
logger.warning(f"Unknown timezone in TZ environment variable: {tz_env}")
67+
68+
# Try system timezone
69+
try:
70+
# Get system timezone
71+
local_tz_name = time.tzname[time.daylight] if time.daylight else time.tzname[0]
72+
if local_tz_name:
73+
# Try to map common abbreviations to full timezone names
74+
tz_mapping = {
75+
'CET': 'Europe/Amsterdam',
76+
'CEST': 'Europe/Amsterdam',
77+
'EST': 'America/New_York',
78+
'EDT': 'America/New_York',
79+
'PST': 'America/Los_Angeles',
80+
'PDT': 'America/Los_Angeles',
81+
'UTC': 'UTC',
82+
'GMT': 'UTC'
83+
}
84+
85+
full_tz_name = tz_mapping.get(local_tz_name, local_tz_name)
86+
return pytz.timezone(full_tz_name)
87+
except:
88+
pass
89+
90+
# Fallback to UTC
91+
logger.warning("Could not detect local timezone, using UTC")
92+
return pytz.UTC
93+
94+
LOCAL_TZ = get_local_timezone()
95+
logger.info(f"Using timezone: {LOCAL_TZ}")
96+
97+
def to_local_time(utc_dt):
98+
"""Convert UTC datetime to local time"""
99+
if utc_dt is None:
100+
return None
101+
if utc_dt.tzinfo is None:
102+
# Assume UTC if no timezone info
103+
utc_dt = pytz.utc.localize(utc_dt)
104+
return utc_dt.astimezone(LOCAL_TZ)
105+
106+
# Add Jinja2 filters
107+
@app.template_filter('local_time')
108+
def local_time_filter(utc_dt):
109+
"""Jinja2 filter to convert UTC time to local time"""
110+
return to_local_time(utc_dt)
111+
112+
@app.template_filter('format_local_time')
113+
def format_local_time_filter(utc_dt, format_str='%Y-%m-%d %H:%M'):
114+
"""Jinja2 filter to format UTC time as local time"""
115+
local_dt = to_local_time(utc_dt)
116+
if local_dt is None:
117+
return "Never"
118+
119+
# Get timezone abbreviation
120+
tz_name = local_dt.strftime('%Z')
121+
if not tz_name: # Fallback if %Z doesn't work
122+
tz_name = str(LOCAL_TZ).split('/')[-1] if '/' in str(LOCAL_TZ) else str(LOCAL_TZ)
123+
124+
return f"{local_dt.strftime(format_str)} {tz_name}"
125+
55126
# Immediate connectivity test (runs once at startup)
56127
from sqlalchemy import text
57128
with app.app_context():
@@ -417,7 +488,15 @@ def backup_jobs():
417488

418489
@app.route('/health')
419490
def health_check():
420-
return jsonify({'status': 'healthy', 'timestamp': datetime.utcnow().isoformat()})
491+
local_time = datetime.now(LOCAL_TZ)
492+
utc_time = datetime.utcnow()
493+
return jsonify({
494+
'status': 'healthy',
495+
'utc_time': utc_time.isoformat(),
496+
'local_time': local_time.isoformat(),
497+
'timezone': str(LOCAL_TZ),
498+
'timezone_name': local_time.strftime('%Z')
499+
})
421500

422501
@app.route('/api/scheduler/status')
423502
@login_required
@@ -508,13 +587,13 @@ def backup_with_context():
508587

509588
# Create new schedule based on schedule_type
510589
if repository.schedule_type == 'hourly':
511-
trigger = CronTrigger(minute=0)
590+
trigger = CronTrigger(minute=0, timezone=LOCAL_TZ)
512591
elif repository.schedule_type == 'daily':
513-
trigger = CronTrigger(hour=2, minute=0) # 2 AM daily
592+
trigger = CronTrigger(hour=2, minute=0, timezone=LOCAL_TZ) # 2 AM local time
514593
elif repository.schedule_type == 'weekly':
515-
trigger = CronTrigger(day_of_week=0, hour=2, minute=0) # Sunday 2 AM
594+
trigger = CronTrigger(day_of_week=0, hour=2, minute=0, timezone=LOCAL_TZ) # Sunday 2 AM local time
516595
elif repository.schedule_type == 'monthly':
517-
trigger = CronTrigger(day=1, hour=2, minute=0) # 1st of month 2 AM
596+
trigger = CronTrigger(day=1, hour=2, minute=0, timezone=LOCAL_TZ) # 1st of month 2 AM local time
518597
elif repository.schedule_type == 'custom':
519598
# Handle custom schedule
520599
hour = repository.custom_hour or 2
@@ -525,40 +604,40 @@ def backup_with_context():
525604
if unit == 'days':
526605
# For daily intervals, use interval_trigger if more than 1 day
527606
if interval == 1:
528-
trigger = CronTrigger(hour=hour, minute=minute) # Daily
607+
trigger = CronTrigger(hour=hour, minute=minute, timezone=LOCAL_TZ) # Daily
529608
else:
530609
# Use interval trigger for multi-day schedules
531610
from apscheduler.triggers.interval import IntervalTrigger
532611
from datetime import datetime, time
533-
# Calculate next run time at the specified hour/minute
534-
now = datetime.now()
612+
# Calculate next run time at the specified hour/minute in local timezone
613+
now = datetime.now(LOCAL_TZ)
535614
start_date = now.replace(hour=hour, minute=minute, second=0, microsecond=0)
536615
if start_date <= now:
537-
start_date = start_date.replace(day=start_date.day + 1)
538-
trigger = IntervalTrigger(days=interval, start_date=start_date)
616+
start_date = start_date + timedelta(days=1)
617+
trigger = IntervalTrigger(days=interval, start_date=start_date, timezone=LOCAL_TZ)
539618
elif unit == 'weeks':
540619
# For weekly intervals
541620
if interval == 1:
542-
trigger = CronTrigger(day_of_week=0, hour=hour, minute=minute) # Every Sunday
621+
trigger = CronTrigger(day_of_week=0, hour=hour, minute=minute, timezone=LOCAL_TZ) # Every Sunday
543622
else:
544623
from apscheduler.triggers.interval import IntervalTrigger
545624
from datetime import datetime
546-
now = datetime.now()
625+
now = datetime.now(LOCAL_TZ)
547626
start_date = now.replace(hour=hour, minute=minute, second=0, microsecond=0)
548627
# Find next Sunday
549628
days_until_sunday = (6 - now.weekday()) % 7
550629
if days_until_sunday == 0 and start_date <= now:
551630
days_until_sunday = 7
552-
start_date = start_date.replace(day=start_date.day + days_until_sunday)
553-
trigger = IntervalTrigger(weeks=interval, start_date=start_date)
631+
start_date = start_date + timedelta(days=days_until_sunday)
632+
trigger = IntervalTrigger(weeks=interval, start_date=start_date, timezone=LOCAL_TZ)
554633
elif unit == 'months':
555634
# For monthly intervals
556635
if interval == 1:
557-
trigger = CronTrigger(day=1, hour=hour, minute=minute) # 1st of every month
636+
trigger = CronTrigger(day=1, hour=hour, minute=minute, timezone=LOCAL_TZ) # 1st of every month
558637
else:
559638
from apscheduler.triggers.interval import IntervalTrigger
560639
from datetime import datetime
561-
now = datetime.now()
640+
now = datetime.now(LOCAL_TZ)
562641
start_date = now.replace(day=1, hour=hour, minute=minute, second=0, microsecond=0)
563642
if start_date <= now:
564643
# Move to next month
@@ -567,7 +646,7 @@ def backup_with_context():
567646
else:
568647
start_date = start_date.replace(month=start_date.month + 1)
569648
# Note: Using weeks approximation for months since APScheduler doesn't have months interval
570-
trigger = IntervalTrigger(weeks=interval*4, start_date=start_date)
649+
trigger = IntervalTrigger(weeks=interval*4, start_date=start_date, timezone=LOCAL_TZ)
571650
else:
572651
return # Invalid unit
573652
else:

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ sqlalchemy==2.0.43
1212
flask-sqlalchemy==3.1.1
1313
wtforms==3.2.1
1414
gunicorn==23.0.0
15+
pytz==2024.1
1516

templates/backup_jobs.html

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
{% extends "base.html" %}
22

3-
{% block page_title %}Backup Jobs{% endblock %}
3+
{% block page_title %}Backu <td>
4+
{% if job.started_at %} <div class="mo <div class="modal-body">
5+
<h6>Repository: {{ job.repository.name }}</h6>
6+
<h6>Job ID: {{ job.id }}</h6>
7+
<h6>Date: {{ job.created_at | format_local_time('%Y-%m-%d %H:%M:%S') }}</h6>
8+
<hr>
9+
<h6>Backup Path:</h6>dy">
10+
<h6>Repository: {{ job.repository.name }}</h6>
11+
<h6>Job ID: {{ job.id }}</h6>
12+
<h6>Date: {{ job.created_at | format_local_time('%Y-%m-%d %H:%M:%S') }}</h6>
13+
<hr>
14+
<h6>Error Message:</h6> {{ job.started_at | format_local_time }}
15+
{% else %}
16+
<span class="text-muted">Not started</span>
17+
{% endif %}
18+
</td>% endblock %}
419

520
{% block content %}
621
<div class="d-flex justify-content-between align-items-center mb-4">

templates/dashboard.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ <h5 class="mb-0">
9696
{{ repo.schedule_type.title() }} backup
9797
{% endif %}
9898
{% if repo.last_backup %}
99-
- Last: {{ repo.last_backup.strftime('%Y-%m-%d %H:%M') }}
99+
- Last: {{ repo.last_backup | format_local_time }}
100100
{% endif %}
101101
</small>
102102
</div>
@@ -147,7 +147,7 @@ <h5 class="mb-0">
147147
<strong>{{ job.repository.name }}</strong>
148148
<br>
149149
<small class="text-muted">
150-
{{ job.created_at.strftime('%Y-%m-%d %H:%M') }}
150+
{{ job.created_at | format_local_time }}
151151
</small>
152152
</div>
153153
<div>

templates/edit_repository.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ <h4 class="mb-0">
116116
{% if repository.last_backup %}
117117
<div class="alert alert-info">
118118
<i class="fas fa-info-circle"></i>
119-
<strong>Last Backup:</strong> {{ repository.last_backup.strftime('%Y-%m-%d %H:%M:%S UTC') }}
119+
<strong>Last Backup:</strong> {{ repository.last_backup | format_local_time('%Y-%m-%d %H:%M:%S') }}
120120
</div>
121121
{% endif %}
122122

templates/repositories.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ <h5 class="card-title">
6464
<p class="card-text">
6565
<small class="text-muted">
6666
<i class="fas fa-clock"></i>
67-
Last backup: {{ repo.last_backup.strftime('%Y-%m-%d %H:%M') }}
67+
Last backup: {{ repo.last_backup | format_local_time }}
6868
</small>
6969
</p>
7070
{% endif %}

0 commit comments

Comments
 (0)