Skip to content

Commit c45bcf6

Browse files
committed
WIP
1 parent 5238dd0 commit c45bcf6

File tree

6 files changed

+126
-98
lines changed

6 files changed

+126
-98
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ doc/source/configspec.rst
1515
.mypy_cache/
1616
.env
1717
.venv
18+
.venv*
1819
env/
1920
venv/
2021
.hypothesis/

khal/controllers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -556,7 +556,7 @@ def edit_event(event, collection, locale, allow_quit=False, width=80):
556556
until = prompt('until (or "None")', until)
557557
if until == 'None':
558558
until = None
559-
rrule = parse_datetime.rrulefstr(freq, until, locale, event.start.tzinfo)
559+
rrule = parse_datetime.rrulefstr(freq, until, locale)
560560
event.update_rrule(rrule)
561561
edited = True
562562
elif choice == "alarm":

khal/icalendar.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,8 @@ def new_vevent(locale,
116116
if not allday and timezone is not None:
117117
assert isinstance(dtstart, dt.datetime)
118118
assert isinstance(dtend, dt.datetime)
119-
dtstart = timezone.localize(dtstart)
120-
dtend = timezone.localize(dtend)
119+
assert dtstart.tzinfo is not None
120+
assert dtend.tzinfo is not None
121121

122122
event = icalendar.Event()
123123
event.add('dtstart', dtstart)
@@ -136,7 +136,7 @@ def new_vevent(locale,
136136
if url:
137137
event.add('url', icalendar.vUri(url))
138138
if repeat and repeat != "none":
139-
rrule = rrulefstr(repeat, until, locale, getattr(dtstart, 'tzinfo', None))
139+
rrule = rrulefstr(repeat, until, locale)
140140
event.add('rrule', rrule)
141141
if alarms:
142142
for alarm in str2alarm(alarms, description or ''):

khal/parse_datetime.py

Lines changed: 56 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
logger = logging.getLogger('khal')
3939

4040

41-
def timefstr(dtime_list: List[str], timeformat: str) -> dt.datetime:
41+
def timefstr(dtime_list: List[str], timeformat: str, timezone: dt.tzinfo) -> dt.datetime:
4242
"""converts the first item of a list (a time as a string) to a datetimeobject
4343
4444
where the date is today and the time is given by a string
@@ -48,8 +48,8 @@ def timefstr(dtime_list: List[str], timeformat: str) -> dt.datetime:
4848
raise ValueError()
4949
datetime_start = dt.datetime.strptime(dtime_list[0], timeformat)
5050
time_start = dt.time(*datetime_start.timetuple()[3:5])
51-
day_start = dt.date.today()
52-
dtstart = dt.datetime.combine(day_start, time_start)
51+
today = dt.datetime.now(timezone).date()
52+
dtstart = dt.datetime.combine(today, time_start).replace(tzinfo=timezone)
5353
dtime_list.pop(0)
5454
return dtstart
5555

@@ -60,10 +60,11 @@ def datetimefstr(
6060
default_day: Optional[dt.date]=None,
6161
infer_year: bool=True,
6262
in_future: bool=True,
63+
timezone: dt.tzinfo=pytz.UTC,
6364
) -> dt.datetime:
6465
"""converts a datetime (as one or several string elements of a list) to
6566
a datetimeobject, if infer_year is True, use the `default_day`'s year as
66-
the year of the return datetimeobject,
67+
the year of the returned datetimeobject,
6768
6869
removes "used" elements of list
6970
@@ -73,7 +74,7 @@ def datetimefstr(
7374
dateformat = '%d.%m. %H:%M'
7475
"""
7576
# if now() is called as default param, mocking with freezegun won't work
76-
now = dt.datetime.now()
77+
now = dt.datetime.now(timezone)
7778
if default_day is None:
7879
default_day = now.date()
7980
parts = dateformat.count(' ') + 1
@@ -92,13 +93,17 @@ def datetimefstr(
9293

9394
if infer_year:
9495
dtstart = dt.datetime(*(default_day.timetuple()[:1] + dtstart_struct[1:5]))
96+
dtstart = dtstart.astimezone(timezone)
9597
if in_future and dtstart < now:
9698
dtstart = dtstart.replace(year=dtstart.year + 1)
9799
if dtstart.date() < default_day:
98100
dtstart = dtstart.replace(year=default_day.year + 1)
101+
assert dtstart.tzinfo is not None
99102
return dtstart
100103
else:
101-
return dt.datetime(*dtstart_struct[:5])
104+
rdt = dt.datetime(*dtstart_struct[:5]).replace(tzinfo=timezone)
105+
assert rdt.tzinfo is not None
106+
return rdt
102107

103108

104109
def weekdaypstr(dayname: str) -> int:
@@ -125,12 +130,12 @@ def weekdaypstr(dayname: str) -> int:
125130
raise ValueError('invalid weekday name `%s`' % dayname)
126131

127132

128-
def construct_daynames(date_: dt.date, local_timezone) -> str:
133+
def construct_daynames(date_: dt.date, timezone: dt.tzinfo) -> str:
129134
"""converts datetime.date into a string description
130135
131136
either `Today`, `Tomorrow` or name of weekday.
132137
"""
133-
today = dt.datetime.now(local_timezone).date()
138+
today = dt.datetime.now(timezone).date()
134139
if date_ == today:
135140
return 'Today'
136141
elif date_ == today + dt.timedelta(days=1):
@@ -139,13 +144,14 @@ def construct_daynames(date_: dt.date, local_timezone) -> str:
139144
return date_.strftime('%A')
140145

141146

142-
def calc_day(dayname: str) -> dt.datetime:
147+
def calc_day(dayname: str, timezone: dt.tzinfo) -> dt.datetime:
143148
"""converts a relative date's description to a datetime object
144149
145150
:param dayname: relative day name (like 'today' or 'monday')
151+
:param timezone: timezone to use for the calculation
146152
:returns: date
147153
"""
148-
today = dt.datetime.combine(dt.date.today(), dt.time.min)
154+
today = dt.datetime.combine(dt.date.today(), dt.time.min).replace(tzinfo=timezone)
149155
dayname = dayname.lower()
150156
if dayname == 'today':
151157
return today
@@ -161,7 +167,7 @@ def calc_day(dayname: str) -> dt.datetime:
161167
return day
162168

163169

164-
def datefstr_weekday(dtime_list: List[str], timeformat: str, infer_year: bool) -> dt.datetime:
170+
def datefstr_weekday(dtime_list: List[str], timeformat: str, infer_year: bool, timezone: dt.tzinfo) -> dt.datetime:
165171
"""interprets first element of a list as a relative date and removes that
166172
element
167173
@@ -172,22 +178,22 @@ def datefstr_weekday(dtime_list: List[str], timeformat: str, infer_year: bool) -
172178
"""
173179
if len(dtime_list) == 0:
174180
raise ValueError()
175-
day = calc_day(dtime_list[0])
181+
day = calc_day(dtime_list[0], timezone=timezone)
176182
dtime_list.pop(0)
177183
return day
178184

179185

180-
def datetimefstr_weekday(dtime_list: List[str], timeformat: str, infer_year: bool) -> dt.datetime:
186+
def datetimefstr_weekday(dtime_list: List[str], timeformat: str, infer_year: bool, timezone: dt.tzinfo) -> dt.datetime:
181187
"""
182188
:param infer_year: only here for compat reasons (having the same function signature)
183189
"""
184190
if len(dtime_list) == 0:
185191
raise ValueError()
186-
day = calc_day(dtime_list[0])
187-
this_time = timefstr(dtime_list[1:], timeformat)
192+
day = calc_day(dtime_list[0], timezone=timezone)
193+
this_time = timefstr(dtime_list[1:], timeformat, timezone)
188194
dtime_list.pop(0)
189-
dtime_list.pop(0) # we need to pop twice as timefstr gets a copy
190-
dtime = dt.datetime.combine(day, this_time.time())
195+
dtime_list.pop(0) # we need to pop twice as timefstr gets a copy and does't remove a used element
196+
dtime = dt.datetime.combine(day, this_time.time()).replace(tzinfo=timezone)
191197
return dtime
192198

193199

@@ -200,27 +206,33 @@ def guessdatetimefstr(
200206
"""
201207
:param in_future: if set, shortdate(time) events will be set in the future
202208
"""
209+
orig = list(dtime_list) # TODO remove this line -- only for debugging
203210
# if now() is called as default param, mocking with freezegun won't work
204-
day = default_day or dt.datetime.now().date()
211+
day = default_day or dt.datetime.now(locale['local_timezone']).date()
205212
# TODO rename in guessdatetimefstrLIST or something saner altogether
206213

207-
def timefstr_day(dtime_list: List[str], timeformat: str, infer_year: bool) -> dt.datetime:
214+
def timefstr_day(dtime_list: List[str], timeformat: str, infer_year: bool, timezone: dt.tzinfo) -> dt.datetime:
208215
if locale['timeformat'] == '%H:%M' and dtime_list[0] == '24:00':
209-
a_date = dt.datetime.combine(day, dt.time(0))
216+
a_date = dt.datetime.combine(day, dt.time(0)).replace(tzinfo=timezone)
210217
dtime_list.pop(0)
211218
else:
212-
a_date = timefstr(dtime_list, timeformat)
213-
a_date = dt.datetime(*(day.timetuple()[:3] + a_date.timetuple()[3:5]))
219+
a_date = timefstr(dtime_list, timeformat, timezone=timezone)
220+
a_date = dt.datetime(*(day.timetuple()[:3] + a_date.timetuple()[3:5])).replace(tzinfo=timezone)
221+
assert a_date.tzinfo is not None
214222
return a_date
215223

216-
def datetimefwords(dtime_list: List[str], _: str, infer_year: bool) -> dt.datetime:
224+
def datetimefwords(dtime_list: List[str], _: str, infer_year: bool, timezone: dt.tzinfo) -> dt.datetime:
225+
"""converts words to datetimes
226+
227+
for now, this only knows "now"
228+
"""
217229
if len(dtime_list) > 0 and dtime_list[0].lower() == 'now':
218230
dtime_list.pop(0)
219-
return dt.datetime.now()
231+
return dt.datetime.now(timezone)
220232
raise ValueError
221233

222-
def datefstr_year(dtime_list: List[str], dtformat: str, infer_year: bool) -> dt.datetime:
223-
return datetimefstr(dtime_list, dtformat, day, infer_year, in_future)
234+
def datefstr_year(dtime_list: List[str], dtformat: str, infer_year: bool, timezone: dt.tzinfo) -> dt.datetime:
235+
return datetimefstr(dtime_list, dtformat, day, infer_year, in_future, timezone)
224236

225237
dtstart = None
226238
fun: Callable[[List[str], str, bool], dt.datetime]
@@ -241,10 +253,12 @@ def datefstr_year(dtime_list: List[str], dtformat: str, infer_year: bool) -> dt.
241253
if infer_year and '97' in dt.datetime(1997, 10, 11).strftime(dtformat):
242254
infer_year = False
243255
try:
244-
dtstart = fun(dtime_list, dtformat, infer_year=infer_year)
256+
timezone = locale['local_timezone']
257+
dtstart = fun(dtime_list, dtformat, infer_year=infer_year, timezone=timezone)
245258
except (ValueError, DateTimeParseError):
246259
pass
247260
else:
261+
assert dtstart.tzinfo is not None
248262
return dtstart, all_day
249263
raise DateTimeParseError(
250264
f"Could not parse \"{dtime_list}\".\nPlease check your configuration "
@@ -341,9 +355,13 @@ def guessrangefstr(daterange: Union[str, List[str]],
341355
range_list = daterange.split(' ')
342356
assert isinstance(range_list, list)
343357

358+
orig = list(range_list) # TODO remove this line -- only for debugging
359+
344360
if range_list == ['week']:
345361
today_weekday = dt.datetime.today().weekday()
346-
startdt = dt.datetime.today() - dt.timedelta(days=(today_weekday - locale['firstweekday']))
362+
today = dt.datetime.now(locale['local_timezone']).date()
363+
today = dt.datetime.combine(today, dt.time.min).replace(tzinfo=locale['local_timezone'])
364+
startdt = today - dt.timedelta(days=(today_weekday - locale['firstweekday']))
347365
enddt = startdt + dt.timedelta(days=8)
348366
return startdt, enddt, True
349367

@@ -365,7 +383,7 @@ def guessrangefstr(daterange: Union[str, List[str]],
365383
else:
366384
end = start + default_timedelta_datetime
367385
elif endstr.lower() == 'eod':
368-
end = dt.datetime.combine(start.date(), dt.time.max)
386+
end = dt.datetime.combine(start.date(), dt.time.max).replace(tzinfo=locale['local_timezone'])
369387
elif endstr.lower() == 'week':
370388
start -= dt.timedelta(days=(start.weekday() - locale['firstweekday']))
371389
end = start + dt.timedelta(days=8)
@@ -397,17 +415,22 @@ def guessrangefstr(daterange: Union[str, List[str]],
397415
if adjust_reasonably:
398416
if allday:
399417
# test if end's year is this year, but start's year is not
400-
today = dt.datetime.today()
418+
today = dt.datetime.now(locale['default_timezone']).date()
401419
if end.year == today.year and start.year != today.year:
402420
end = dt.datetime(start.year, *end.timetuple()[1:6])
403421

404422
if end < start:
405423
end = dt.datetime(end.year + 1, *end.timetuple()[1:6])
406424

407425
if end < start:
408-
end = dt.datetime(*start.timetuple()[0:3] + end.timetuple()[3:5])
426+
# if end is before start date we are using the year, month and day of start
427+
# and the time of end to create a new end date
428+
end = dt.datetime(*start.timetuple()[0:3] + end.timetuple()[3:5]).replace(tzinfo=locale['local_timezone'])
409429
if end < start:
430+
# if end is still before start date we are adding a day
410431
end = end + dt.timedelta(days=1)
432+
assert start.tzinfo is not None
433+
assert end.tzinfo is not None
411434
return start, end, allday
412435
except (ValueError, DateTimeParseError):
413436
pass
@@ -423,18 +446,13 @@ def guessrangefstr(daterange: Union[str, List[str]],
423446
def rrulefstr(repeat: str,
424447
until: str,
425448
locale: LocaleConfiguration,
426-
timezone: Optional[dt.tzinfo],
427449
) -> RRuleMapType:
428450
if repeat in ["daily", "weekly", "monthly", "yearly"]:
429451
rrule_settings: RRuleMapType = {'freq': repeat}
430452
if until:
431453
until_dt, _ = guessdatetimefstr(until.split(' '), locale)
432-
if timezone:
433-
rrule_settings['until'] = until_dt.\
434-
replace(tzinfo=timezone).\
435-
astimezone(pytz.UTC)
436-
else:
437-
rrule_settings['until'] = until_dt
454+
assert until_dt.tzinfo is not None
455+
rrule_settings['until'] = until_dt
438456
return rrule_settings
439457
else:
440458
logger.fatal("Invalid value for the repeat option. \

khal/ui/__init__.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,12 @@ def relative_day(self, day: dt.date, dtformat: str) -> str:
130130

131131
weekday = day.strftime('%A')
132132
daystr = day.strftime(dtformat)
133-
if day == dt.date.today():
133+
today = dt.datetime.now(self._conf['locale']['local_timezone']).date()
134+
if day == today:
134135
return f'Today ({weekday}, {daystr})'
135-
elif day == dt.date.today() + dt.timedelta(days=1):
136+
elif day == today + dt.timedelta(days=1):
136137
return f'Tomorrow ({weekday}, {daystr})'
137-
elif day == dt.date.today() - dt.timedelta(days=1):
138+
elif day == today - dt.timedelta(days=1):
138139
return f'Yesterday ({weekday}, {daystr})'
139140

140141
approx_delta = utils.relative_timedelta_str(day)

0 commit comments

Comments
 (0)