_localOffsetForDate() was broken

It was including the elapsed time of day when calculating
the offset, leading to incorrect results
This commit is contained in:
Damien Elmes 2020-01-17 18:52:36 -07:00
parent fa56477205
commit 12c60f20fe
6 changed files with 43 additions and 27 deletions

View file

@ -15,6 +15,7 @@ message BackendInput {
FindCardsIn find_cards = 19;
BrowserRowsIn browser_rows = 20;
RenderCardIn render_card = 21;
int64 local_minutes_west = 22;
PlusOneIn plus_one = 2046; // temporary, for testing
}
@ -28,6 +29,7 @@ message BackendOutput {
FindCardsOut find_cards = 19;
BrowserRowsOut browser_rows = 20;
RenderCardOut render_card = 21;
sint32 local_minutes_west = 22;
PlusOneOut plus_one = 2046; // temporary, for testing

View file

@ -132,7 +132,7 @@ class _Collection:
elif ver == 2:
self.sched = V2Scheduler(self)
if not self.server:
self.conf["localOffset"] = self.sched.currentTimezoneOffset()
self.conf["localOffset"] = self.sched._current_timezone_offset()
elif self.server.minutes_west is not None:
self.conf["localOffset"] = self.server.minutes_west
@ -162,7 +162,7 @@ class _Collection:
if isinstance(self.sched, V1Scheduler):
return None
else:
return self.sched.currentTimezoneOffset()
return self.sched._current_timezone_offset()
# DB-related
##########################################################################

View file

@ -143,3 +143,8 @@ class RustBackend:
anodes = proto_replacement_list_to_native(out.answer_nodes) # type: ignore
return (qnodes, anodes)
def local_minutes_west(self, stamp: int) -> int:
return self._run_command(
pb.BackendInput(local_minutes_west=stamp)
).local_minutes_west

View file

@ -1340,9 +1340,9 @@ where id = ?
def _updateCutoff(self) -> None:
oldToday = self.today
timing = self._timingToday()
timing = self._timing_today()
if self._newTimezoneEnabled():
if self._new_timezone_enabled():
self.today = timing.days_elapsed
self.dayCutoff = timing.next_day_at
else:
@ -1398,41 +1398,40 @@ where id = ?
# New timezone handling
##########################################################################
def _newTimezoneEnabled(self) -> bool:
def _new_timezone_enabled(self) -> bool:
return self.col.conf.get("creationOffset") is not None
def _timingToday(self) -> SchedTimingToday:
def _timing_today(self) -> SchedTimingToday:
return self.col.backend.sched_timing_today(
self.col.crt,
self.creationTimezoneOffset(),
self._creation_timezone_offset(),
intTime(),
self.currentTimezoneOffset(),
self._current_timezone_offset(),
self._rolloverHour(),
)
def currentTimezoneOffset(self) -> int:
def _current_timezone_offset(self) -> int:
if self.col.server:
return self.col.conf.get("localOffset", 0)
else:
return self._localOffsetForDate(datetime.datetime.today())
return self.col.backend.local_minutes_west(intTime())
def creationTimezoneOffset(self) -> int:
def _creation_timezone_offset(self) -> int:
return self.col.conf.get("creationOffset", 0)
def setCreationOffset(self):
def set_creation_offset(self):
"""Save the UTC west offset at the time of creation into the DB.
Once stored, this activates the new timezone handling code.
"""
mins_west = self._localOffsetForDate(
datetime.datetime.fromtimestamp(self.col.crt)
)
mins_west = self.col.backend.local_minutes_west(self.col.crt)
self.col.conf["creationOffset"] = mins_west
self.col.setMod()
def _localOffsetForDate(self, date: datetime.datetime) -> int:
"Minutes west of UTC for a given datetime in the local timezone."
return date.astimezone().utcoffset().seconds // -60
def clear_creation_offset(self):
if "creationOffset" in self.col.conf:
del self.col.conf["creationOffset"]
self.col.setMod()
# Deck finished state
##########################################################################

View file

@ -5,7 +5,7 @@ use crate::backend_proto as pt;
use crate::backend_proto::backend_input::Value;
use crate::backend_proto::RenderedTemplateReplacement;
use crate::err::{AnkiError, Result};
use crate::sched::sched_timing_today;
use crate::sched::{local_minutes_west_for_stamp, sched_timing_today};
use crate::template::{
render_card, without_legacy_template_directives, FieldMap, FieldRequirements, ParsedTemplate,
RenderedNode,
@ -96,6 +96,9 @@ impl Backend {
Value::FindCards(_) => todo!(),
Value::BrowserRows(_) => todo!(),
Value::RenderCard(input) => OValue::RenderCard(self.render_template(input)?),
Value::LocalMinutesWest(stamp) => {
OValue::LocalMinutesWest(local_minutes_west_for_stamp(stamp))
}
})
}

View file

@ -82,21 +82,21 @@ fn fixed_offset_from_minutes(minutes_west: i32) -> FixedOffset {
FixedOffset::west(bounded_minutes * 60)
}
/// Relative to the local timezone, the number of minutes UTC differs by.
/// For the given timestamp, return minutes west of UTC in the
/// local timezone.
/// eg, Australia at +10 hours is -600.
/// Includes the daylight savings offset if applicable.
#[allow(dead_code)]
fn utc_minus_local_mins() -> i32 {
Local::now().offset().utc_minus_local() / 60
pub fn local_minutes_west_for_stamp(stamp: i64) -> i32 {
Local.timestamp(stamp, 0).offset().utc_minus_local() / 60
}
#[cfg(test)]
mod test {
use crate::sched::{
fixed_offset_from_minutes, normalized_rollover_hour, sched_timing_today,
utc_minus_local_mins,
fixed_offset_from_minutes, local_minutes_west_for_stamp, normalized_rollover_hour,
sched_timing_today,
};
use chrono::{FixedOffset, Local, TimeZone};
use chrono::{FixedOffset, Local, TimeZone, Utc};
#[test]
fn test_rollover() {
@ -121,9 +121,16 @@ mod test {
today.days_elapsed
}
#[test]
fn test_local_minutes_west() {
// -480 throughout the year
std::env::set_var("TZ", "Australia/Perth");
assert_eq!(local_minutes_west_for_stamp(Utc::now().timestamp()), -480);
}
#[test]
fn test_days_elapsed() {
let local_offset = utc_minus_local_mins();
let local_offset = local_minutes_west_for_stamp(Utc::now().timestamp());
let created_dt = FixedOffset::west(local_offset * 60)
.ymd(2019, 12, 1)