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

View file

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

View file

@ -143,3 +143,8 @@ class RustBackend:
anodes = proto_replacement_list_to_native(out.answer_nodes) # type: ignore anodes = proto_replacement_list_to_native(out.answer_nodes) # type: ignore
return (qnodes, anodes) 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: def _updateCutoff(self) -> None:
oldToday = self.today oldToday = self.today
timing = self._timingToday() timing = self._timing_today()
if self._newTimezoneEnabled(): if self._new_timezone_enabled():
self.today = timing.days_elapsed self.today = timing.days_elapsed
self.dayCutoff = timing.next_day_at self.dayCutoff = timing.next_day_at
else: else:
@ -1398,41 +1398,40 @@ where id = ?
# New timezone handling # New timezone handling
########################################################################## ##########################################################################
def _newTimezoneEnabled(self) -> bool: def _new_timezone_enabled(self) -> bool:
return self.col.conf.get("creationOffset") is not None 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( return self.col.backend.sched_timing_today(
self.col.crt, self.col.crt,
self.creationTimezoneOffset(), self._creation_timezone_offset(),
intTime(), intTime(),
self.currentTimezoneOffset(), self._current_timezone_offset(),
self._rolloverHour(), self._rolloverHour(),
) )
def currentTimezoneOffset(self) -> int: def _current_timezone_offset(self) -> int:
if self.col.server: if self.col.server:
return self.col.conf.get("localOffset", 0) return self.col.conf.get("localOffset", 0)
else: 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) 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. """Save the UTC west offset at the time of creation into the DB.
Once stored, this activates the new timezone handling code. Once stored, this activates the new timezone handling code.
""" """
mins_west = self._localOffsetForDate( mins_west = self.col.backend.local_minutes_west(self.col.crt)
datetime.datetime.fromtimestamp(self.col.crt)
)
self.col.conf["creationOffset"] = mins_west self.col.conf["creationOffset"] = mins_west
self.col.setMod() self.col.setMod()
def _localOffsetForDate(self, date: datetime.datetime) -> int: def clear_creation_offset(self):
"Minutes west of UTC for a given datetime in the local timezone." if "creationOffset" in self.col.conf:
return date.astimezone().utcoffset().seconds // -60 del self.col.conf["creationOffset"]
self.col.setMod()
# Deck finished state # 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::backend_input::Value;
use crate::backend_proto::RenderedTemplateReplacement; use crate::backend_proto::RenderedTemplateReplacement;
use crate::err::{AnkiError, Result}; 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::{ use crate::template::{
render_card, without_legacy_template_directives, FieldMap, FieldRequirements, ParsedTemplate, render_card, without_legacy_template_directives, FieldMap, FieldRequirements, ParsedTemplate,
RenderedNode, RenderedNode,
@ -96,6 +96,9 @@ impl Backend {
Value::FindCards(_) => todo!(), Value::FindCards(_) => todo!(),
Value::BrowserRows(_) => todo!(), Value::BrowserRows(_) => todo!(),
Value::RenderCard(input) => OValue::RenderCard(self.render_template(input)?), 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) 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. /// eg, Australia at +10 hours is -600.
/// Includes the daylight savings offset if applicable. /// Includes the daylight savings offset if applicable.
#[allow(dead_code)] pub fn local_minutes_west_for_stamp(stamp: i64) -> i32 {
fn utc_minus_local_mins() -> i32 { Local.timestamp(stamp, 0).offset().utc_minus_local() / 60
Local::now().offset().utc_minus_local() / 60
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::sched::{ use crate::sched::{
fixed_offset_from_minutes, normalized_rollover_hour, sched_timing_today, fixed_offset_from_minutes, local_minutes_west_for_stamp, normalized_rollover_hour,
utc_minus_local_mins, sched_timing_today,
}; };
use chrono::{FixedOffset, Local, TimeZone}; use chrono::{FixedOffset, Local, TimeZone, Utc};
#[test] #[test]
fn test_rollover() { fn test_rollover() {
@ -121,9 +121,16 @@ mod test {
today.days_elapsed 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] #[test]
fn test_days_elapsed() { 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) let created_dt = FixedOffset::west(local_offset * 60)
.ymd(2019, 12, 1) .ymd(2019, 12, 1)