Skip to content

Commit 4033a27

Browse files
author
Wanasit Tanakitrungruang
committed
Fix/New: Reference date calculation base-on timezone adjusted instant
1 parent fe47db2 commit 4033a27

File tree

11 files changed

+307
-113
lines changed

11 files changed

+307
-113
lines changed

src/common/casualReferences.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,17 @@ import {
1010
import { Meridiem } from "../types";
1111

1212
export function now(reference: ReferenceWithTimezone): ParsingComponents {
13-
const targetDate = dayjs(reference.instant);
13+
const targetDate = dayjs(reference.getDateWithAdjustedTimezone());
1414
const component = new ParsingComponents(reference, {});
1515
assignSimilarDate(component, targetDate);
1616
assignSimilarTime(component, targetDate);
17-
if (reference.timezoneOffset !== null) {
18-
component.assign("timezoneOffset", targetDate.utcOffset());
19-
}
17+
component.assign("timezoneOffset", reference.getTimezoneOffset());
2018
component.addTag("casualReference/now");
2119
return component;
2220
}
2321

2422
export function today(reference: ReferenceWithTimezone): ParsingComponents {
25-
const targetDate = dayjs(reference.instant);
23+
const targetDate = dayjs(reference.getDateWithAdjustedTimezone());
2624
const component = new ParsingComponents(reference, {});
2725
assignSimilarDate(component, targetDate);
2826
implySimilarTime(component, targetDate);
@@ -49,7 +47,7 @@ export function tomorrow(reference: ReferenceWithTimezone): ParsingComponents {
4947
}
5048

5149
export function theDayAfter(reference: ReferenceWithTimezone, nDays: number): ParsingComponents {
52-
let targetDate = dayjs(reference.instant);
50+
let targetDate = dayjs(reference.getDateWithAdjustedTimezone());
5351
const component = new ParsingComponents(reference, {});
5452
targetDate = targetDate.add(nDays, "day");
5553
assignSimilarDate(component, targetDate);
@@ -58,7 +56,7 @@ export function theDayAfter(reference: ReferenceWithTimezone, nDays: number): Pa
5856
}
5957

6058
export function tonight(reference: ReferenceWithTimezone, implyHour = 22): ParsingComponents {
61-
const targetDate = dayjs(reference.instant);
59+
const targetDate = dayjs(reference.getDateWithAdjustedTimezone());
6260
const component = new ParsingComponents(reference, {});
6361
assignSimilarDate(component, targetDate);
6462
component.imply("hour", implyHour);
@@ -68,7 +66,7 @@ export function tonight(reference: ReferenceWithTimezone, implyHour = 22): Parsi
6866
}
6967

7068
export function lastNight(reference: ReferenceWithTimezone, implyHour = 0): ParsingComponents {
71-
let targetDate = dayjs(reference.instant);
69+
let targetDate = dayjs(reference.getDateWithAdjustedTimezone());
7270
const component = new ParsingComponents(reference, {});
7371
if (targetDate.hour() < 6) {
7472
targetDate = targetDate.add(-1, "day");
@@ -87,7 +85,7 @@ export function evening(reference: ReferenceWithTimezone, implyHour = 20): Parsi
8785
}
8886

8987
export function yesterdayEvening(reference: ReferenceWithTimezone, implyHour = 20): ParsingComponents {
90-
let targetDate = dayjs(reference.instant);
88+
let targetDate = dayjs(reference.getDateWithAdjustedTimezone());
9189
const component = new ParsingComponents(reference, {});
9290
targetDate = targetDate.add(-1, "day");
9391
assignSimilarDate(component, targetDate);
@@ -100,7 +98,7 @@ export function yesterdayEvening(reference: ReferenceWithTimezone, implyHour = 2
10098

10199
export function midnight(reference: ReferenceWithTimezone): ParsingComponents {
102100
const component = new ParsingComponents(reference, {});
103-
const targetDate = dayjs(reference.instant);
101+
const targetDate = dayjs(reference.getDateWithAdjustedTimezone());
104102
if (targetDate.hour() > 2) {
105103
// Unless it's very early morning (0~2AM), we assume the midnight is the coming midnight.
106104
// Thus, increasing the day by 1.

src/common/refiners/ForwardDateRefiner.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { ParsingContext, Refiner } from "../../chrono";
88
import { ParsingResult } from "../../results";
99
import dayjs from "dayjs";
1010
import { implySimilarDate } from "../../utils/dayjs";
11+
import * as dates from "../../utils/dates";
1112

1213
export default class ForwardDateRefiner implements Refiner {
1314
refine(context: ParsingContext, results: ParsingResult[]): ParsingResult[] {
@@ -16,21 +17,26 @@ export default class ForwardDateRefiner implements Refiner {
1617
}
1718

1819
results.forEach((result) => {
19-
let refMoment = dayjs(context.refDate);
20+
let refMoment = dayjs(context.reference.getDateWithAdjustedTimezone());
2021

21-
if (result.start.isOnlyTime() && refMoment.isAfter(result.start.dayjs())) {
22-
refMoment = refMoment.add(1, "day");
23-
implySimilarDate(result.start, refMoment);
22+
if (result.start.isOnlyTime() && context.reference.instant > result.start.date()) {
23+
const refDate = context.reference.getDateWithAdjustedTimezone();
24+
const refFollowingDay = new Date(refDate);
25+
refFollowingDay.setDate(refFollowingDay.getDate() + 1);
26+
27+
dates.implySimilarDate(result.start, refFollowingDay);
28+
context.debug(() => {
29+
console.log(
30+
`${this.constructor.name} adjusted ${result} time from the ref date (${refDate}) to the following day (${refFollowingDay})`
31+
);
32+
});
2433
if (result.end && result.end.isOnlyTime()) {
25-
implySimilarDate(result.end, refMoment);
26-
if (result.start.dayjs().isAfter(result.end.dayjs())) {
27-
refMoment = refMoment.add(1, "day");
28-
implySimilarDate(result.end, refMoment);
34+
dates.implySimilarDate(result.end, refFollowingDay);
35+
if (result.start.date() > result.end.date()) {
36+
refFollowingDay.setDate(refFollowingDay.getDate() + 1);
37+
dates.implySimilarDate(result.end, refFollowingDay);
2938
}
3039
}
31-
context.debug(() => {
32-
console.log(`${this.constructor.name} adjusted ${result} time result (${result.start})`);
33-
});
3440
}
3541

3642
if (result.start.isOnlyWeekdayComponent() && refMoment.isAfter(result.start.dayjs())) {

src/results.ts

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export class ReferenceWithTimezone {
1414
input = input ?? new Date();
1515
if (input instanceof Date) {
1616
this.instant = input;
17+
this.timezoneOffset = null;
1718
} else {
1819
this.instant = input.instant ?? new Date();
1920
this.timezoneOffset = toTimezoneOffset(input.timezone, this.instant);
@@ -25,7 +26,11 @@ export class ReferenceWithTimezone {
2526
* The output's instant is NOT the reference's instant when the reference's and system's timezone are different.
2627
*/
2728
getDateWithAdjustedTimezone() {
28-
return new Date(this.instant.getTime() + this.getSystemTimezoneAdjustmentMinute(this.instant) * 60000);
29+
const date = new Date(this.instant);
30+
if (this.timezoneOffset !== null) {
31+
date.setMinutes(date.getMinutes() - this.getSystemTimezoneAdjustmentMinute(this.instant));
32+
}
33+
return date;
2934
}
3035

3136
/**
@@ -44,6 +49,10 @@ export class ReferenceWithTimezone {
4449
const targetTimezoneOffset = overrideTimezoneOffset ?? this.timezoneOffset ?? currentTimezoneOffset;
4550
return currentTimezoneOffset - targetTimezoneOffset;
4651
}
52+
53+
getTimezoneOffset(): number {
54+
return this.timezoneOffset ?? -this.instant.getTimezoneOffset();
55+
}
4756
}
4857

4958
export class ParsingComponents implements ParsedComponents {
@@ -62,10 +71,10 @@ export class ParsingComponents implements ParsedComponents {
6271
}
6372
}
6473

65-
const refDayJs = dayjs(reference.instant);
66-
this.imply("day", refDayJs.date());
67-
this.imply("month", refDayJs.month() + 1);
68-
this.imply("year", refDayJs.year());
74+
const refDayJs = reference.getDateWithAdjustedTimezone();
75+
this.imply("day", refDayJs.getDate());
76+
this.imply("month", refDayJs.getMonth() + 1);
77+
this.imply("year", refDayJs.getFullYear());
6978
this.imply("hour", 12);
7079
this.imply("minute", 0);
7180
this.imply("second", 0);
@@ -166,7 +175,7 @@ export class ParsingComponents implements ParsedComponents {
166175
}
167176

168177
dayjs() {
169-
return dayjs(this.date());
178+
return dayjs(this.dateWithoutTimezoneAdjustment());
170179
}
171180

172181
date(): Date {
@@ -210,7 +219,7 @@ export class ParsingComponents implements ParsedComponents {
210219
reference: ReferenceWithTimezone,
211220
fragments: { [c in QUnitType]?: number }
212221
): ParsingComponents {
213-
let date = dayjs(reference.instant);
222+
let date = dayjs(reference.getDateWithAdjustedTimezone());
214223
for (const key in fragments) {
215224
date = date.add(fragments[key as QUnitType], key as QUnitType);
216225
}
@@ -221,14 +230,10 @@ export class ParsingComponents implements ParsedComponents {
221230
components.addTag("result/relativeDateAndTime");
222231
assignSimilarTime(components, date);
223232
assignSimilarDate(components, date);
224-
if (reference.timezoneOffset !== null) {
225-
components.assign("timezoneOffset", -reference.instant.getTimezoneOffset());
226-
}
233+
components.assign("timezoneOffset", reference.getTimezoneOffset());
227234
} else {
228235
implySimilarTime(components, date);
229-
if (reference.timezoneOffset !== null) {
230-
components.imply("timezoneOffset", -reference.instant.getTimezoneOffset());
231-
}
236+
components.imply("timezoneOffset", reference.getTimezoneOffset());
232237

233238
if (fragments["d"]) {
234239
components.assign("day", date.date());

src/utils/dates.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { ParsingComponents } from "../results";
2+
3+
/**
4+
* Imply (weakly update) the parsing component to the same day as the `target`.
5+
* @param component the component to be updated.
6+
* @param target the target date with timezone adjusted.
7+
*/
8+
export function implySimilarDate(component: ParsingComponents, target: Date) {
9+
component.imply("day", target.getDate());
10+
component.imply("month", target.getMonth() + 1);
11+
component.imply("year", target.getFullYear());
12+
}
13+
14+
/**
15+
* Imply (weakly update) the parsing component to the same time as the `target`.
16+
* @param component the component to be updated.
17+
* @param target the target date with timezone adjusted.
18+
*/
19+
export function implySimilarTime(component: ParsingComponents, target: Date) {
20+
component.imply("hour", target.getHours());
21+
component.imply("minute", target.getMinutes());
22+
component.imply("second", target.getSeconds());
23+
component.imply("millisecond", target.getMilliseconds());
24+
}

src/utils/dayjs.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,18 @@ export function assignSimilarTime(component: ParsingComponents, targetDayJs: day
3232
}
3333
}
3434

35+
/**
36+
* @deprecated Use `dates.implySimilarDate` with normal Javascript Date instead.
37+
*/
3538
export function implySimilarDate(component: ParsingComponents, targetDayJs: dayjs.Dayjs) {
3639
component.imply("day", targetDayJs.date());
3740
component.imply("month", targetDayJs.month() + 1);
3841
component.imply("year", targetDayJs.year());
3942
}
4043

44+
/**
45+
* @deprecated Use `dates.implySimilarTime` with normal Javascript Date instead.
46+
*/
4147
export function implySimilarTime(component: ParsingComponents, targetDayJs: dayjs.Dayjs) {
4248
component.imply("hour", targetDayJs.hour());
4349
component.imply("minute", targetDayJs.minute());

test/en/en_casual.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ test("Test - Single Expression", () => {
2828
(result) => {
2929
expect(result.text).toBe("now");
3030
expect(result.start).toBeDate(new Date(1637674343000));
31-
expect(result.start.isCertain("timezoneOffset")).toBe(false);
31+
32+
// Although the timezone is not specified, we need to assume it is similar to the system default.
33+
// We also must allow the timezoneOffset to be overridden. Otherwise, the "now" meaning is incorrect.
34+
// expect(result.start.isCertain("timezoneOffset")).toBe(false);
3235
}
3336
);
3437

0 commit comments

Comments
 (0)