threema-ch / threema-web

The Threema Web application.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

logic for determining whether a point in time was yesterday is flawed

das-g opened this issue · comments

Bug Description

The logic for determining whether a point in time was yesterday

const yesterday = new Date(now.getTime() - 1000 * 60 * 60 * 24);
doesn't work correctly around leap seconds and DST changes.

For days longer than 24 h (i.e., leap seconds and "fall back" out of DST), this doesn't matter, as the lines before it

threema-web/src/filters.ts

Lines 381 to 383 in 39c046f

if (!forceFull && isSameDay(date, now)) {
return formatTime(date);
}
will already correctly identify any point in time of the same date as being on the same day, even if that point in time is more than 24 h before now.

For days shorter than 24 h (i.e., "spring forward" into DST), this however meant they will incorrectly be "skipped" when looking from a point in time on the next day but less than 24 h after the beginning of said shorter day (i.e. between 0:00 AM and 1:00 AM on the day after the shorter day) back to points in time within that shorter day. Here's a test to demonstrate the problem:

it('shows "yesterday" for yesterday also around DST change', () => {
// This test relies on being run in timezone Europe/Zurich
// FIXME: Mock timezone, so that test is portable.
const beforeDST1 = new Date(2020, 2, 29, 0, 42);
const beforeDST2 = new Date(2020, 2, 29, 12, 42);
const beforeDST3 = new Date(2020, 2, 29, 23, 59);
const duringDST = new Date(2020, 2, 30, 0, 23);
jasmine.clock().install();
jasmine.clock().mockDate(duringDST);
this.testPatterns([
[beforeDST1.getTime() / 1000, 'date.YESTERDAY, 00:42'],
[beforeDST2.getTime() / 1000, 'date.YESTERDAY, 12:42'],
[beforeDST3.getTime() / 1000, 'date.YESTERDAY, 23:59'],
]);
});
All 3 assertions fail when run in a timezone that switched for standard time to DST on 2020-03-29, such as Europe/Zurich.

Steps to Reproduce (for bugs)

This problem was found in the source code, not by observing the application behavior. But I guess it could be reproduced like so (untested):

  1. Be in a timezone that observes DST
  2. On the day DST begins, send a message.
  3. On the next day, between 0:00 AM (midnight) and 1:00 AM, look at that message

Expected

The day of the message is displayed as "yesterday".

Observed (speculative, as I didn't test this)

The day of the message is displayed its date.

Potential Solution

Use approach from this Stack Overflow answer instead:

threema-web/src/filters.ts

Lines 385 to 386 in a3de764

const yesterday = new Date(now);
yesterday.setDate(now.getDate() - 1);

Your Environment

  • Threema Web version: Source code on master (39c046f)
  • Browser name and version: Firefox 84.0 (64-bit)
  • Computer operating system and version: NixOS unstable:
    • system: "x86_64-linux"
    • host os: Linux 5.4.85, NixOS, 21.03pre259798.84917aa00bf (Okapi)
    • multi-user?: yes
    • sandbox: yes
    • version: nix-env (Nix) 2.3.10
    • channels(root): "nixos-21.03pre259798.84917aa00bf, nixos-unstable-21.03pre257780.e9158eca70a"
    • channels(das-g): ""
    • nixpkgs: /nix/var/nix/profiles/per-user/root/channels/nixos

I'll file a draft Pull Request for this soon.

Edit:

Here's the draft PR: #1014

Thanks! Note that Threema Web is in maintenance mode, but we'll review pull requests!

Thanks! Note that Threema Web is in maintenance mode, but we'll review pull requests!

I've marked #1014 as "ready for review" now, which turned the draft PR into a PR.

Is

to work on that problem. If you have questions, let us know, we'll try to help
you with the implementation.
still accurate? If yes, I'd need some guidance on how to make the new test
it('shows "yesterday" for yesterday also around DST change', () => {
// This test relies on being run in timezone Europe/Zurich
// FIXME: Mock timezone, so that test is portable.
const beforeDST1 = new Date(2020, 2, 29, 0, 42);
const beforeDST2 = new Date(2020, 2, 29, 12, 42);
const beforeDST3 = new Date(2020, 2, 29, 23, 59);
const duringDST = new Date(2020, 2, 30, 0, 23);
jasmine.clock().install();
jasmine.clock().mockDate(duringDST);
this.testPatterns([
[beforeDST1.getTime() / 1000, 'date.YESTERDAY, 00:42'],
[beforeDST2.getTime() / 1000, 'date.YESTERDAY, 12:42'],
[beforeDST3.getTime() / 1000, 'date.YESTERDAY, 23:59'],
]);
});
run with a specific timezone. If not, I guess we could just leave out that new test.