File size: 6,718 Bytes
bf48b89 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 | import dayjs from 'dayjs';
import weekday from 'dayjs/plugin/weekday.js';
import MockDate from 'mockdate';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { parseRelativeDate } from './parse-date';
dayjs.extend(weekday);
describe('parseRelativeDate', () => {
// === CONSTANTS ===
const second = 1000;
const minute = 60 * second;
const hour = 60 * minute;
const day = 24 * hour;
const week = 7 * day;
// === BASE DATE SETUP: 2026-02-02 (Monday) ===
const BASE_STR = '2026-02-02T12:00:00';
const NOW_TIMESTAMP = new Date(BASE_STR).getTime();
// Derived Timestamps
const TODAY_START = new Date('2026-02-02T00:00:00').getTime();
const YESTERDAY_START = TODAY_START - day;
const TOMORROW_START = TODAY_START + day;
// Strict Past Logic Expectations (Reference is Mon Feb 02)
const PREVIOUS_MONDAY = TODAY_START - week; // Jan 26 (Last week)
const PREVIOUS_WEDNESDAY = TODAY_START + 2 * day - week; // Jan 28 (Last week)
const LAST_SUNDAY = new Date('2026-02-01T00:00:00').getTime(); // Yesterday (Feb 01)
const LAST_FRIDAY = new Date('2026-01-30T00:00:00').getTime(); // Last Friday (Jan 30)
const p = (str: string, ...opts: any[]) => parseRelativeDate(str, ...opts).getTime();
beforeEach(() => {
MockDate.set(NOW_TIMESTAMP);
});
afterEach(() => {
MockDate.reset();
});
/**
* Category 1: Immediate & Fuzzy Semantics
*/
describe('Immediate & Fuzzy Semantics', () => {
it('handles "Just now" / "εε"', () => {
const expected = NOW_TIMESTAMP - 3 * second;
expect(p('Just now')).toBe(expected);
expect(p('just now')).toBe(expected);
expect(p('εε')).toBe(expected);
});
it('handles vague quantifiers (x / ε )', () => {
// "ε " maps to 3
expect(p('ε ειε')).toBe(NOW_TIMESTAMP - 3 * minute);
expect(p('εΉΎειε')).toBe(NOW_TIMESTAMP - 3 * minute);
expect(p('ζ°η§ε')).toBe(NOW_TIMESTAMP - 3 * second);
// "x" maps to 3
expect(p('x days ago')).toBe(NOW_TIMESTAMP - 3 * day);
expect(p('X seconds ago')).toBe(NOW_TIMESTAMP - 3 * second);
// "a/an" maps to 1
expect(p('a minute ago')).toBe(NOW_TIMESTAMP - 1 * minute);
});
});
/**
* Category 2: Relative Duration (Ago / In)
*/
describe('Relative Duration', () => {
it('handles past (ago / ε)', () => {
expect(p('10m ago')).toBe(NOW_TIMESTAMP - 10 * minute);
expect(p('2 hours ago')).toBe(NOW_TIMESTAMP - 2 * hour);
expect(p('10η§ε')).toBe(NOW_TIMESTAMP - 10 * second);
});
it('handles future (in / later / ε)', () => {
expect(p('in 10m')).toBe(NOW_TIMESTAMP + 10 * minute);
expect(p('2 hours later')).toBe(NOW_TIMESTAMP + 2 * hour);
expect(p('10ειε')).toBe(NOW_TIMESTAMP + 10 * minute);
expect(p('10 ειεΎ')).toBe(NOW_TIMESTAMP + 10 * minute);
});
it('handles mixed units', () => {
expect(p('1d 1h ago')).toBe(NOW_TIMESTAMP - (day + hour));
});
});
/**
* Category 3: Semantic Keywords & Strict Past Logic
* Reference: Today is Monday, Feb 02.
*/
describe('Semantic Keywords & Weekdays', () => {
it('handles Today/Yesterday/Tomorrow', () => {
expect(p('Today')).toBe(TODAY_START);
expect(p('Yesterday')).toBe(YESTERDAY_START);
expect(p('Tomorrow')).toBe(TOMORROW_START);
});
it('handles "Monday" (Strict Past: Today is Mon -> Last Mon)', () => {
// Strict past rule: If input weekday is Same as today, go back 1 week.
expect(p('Monday')).toBe(PREVIOUS_MONDAY);
expect(p('ε¨δΈ')).toBe(PREVIOUS_MONDAY);
expect(p('ζζδΈ')).toBe(PREVIOUS_MONDAY);
});
it('handles "Monday 3pm" (Strict Past)', () => {
expect(p('Monday 3pm')).toBe(PREVIOUS_MONDAY + 15 * hour);
expect(p('ε¨δΈ 15:00')).toBe(PREVIOUS_MONDAY + 15 * hour);
});
it('handles "Wednesday" (Strict Past: Wed is Future -> Last Wed)', () => {
// Strict past rule: If input weekday is Future, go back 1 week.
// Today is Mon Feb 02. Wed is Feb 04 (Future) -> Back 1 week -> Jan 28.
expect(p('Wednesday')).toBe(PREVIOUS_WEDNESDAY);
expect(p('ε¨δΈ')).toBe(PREVIOUS_WEDNESDAY);
});
it('handles "Sunday" (Past: Sun is Yesterday -> Feb 01)', () => {
// Sunday (Feb 01) is strictly before Monday (Feb 02).
expect(p('Sunday')).toBe(LAST_SUNDAY);
});
it('handles "Friday" (Past: Fri is Jan 30)', () => {
expect(p('Friday')).toBe(LAST_FRIDAY);
});
});
/**
* Category 4: Contextual & Combined Expressions
*/
describe('Contextual & Formatted Time', () => {
it('handles sticky AM/PM', () => {
expect(p('Today 3pm')).toBe(TODAY_START + 15 * hour);
expect(p('Yesterday 10pm')).toBe(YESTERDAY_START + 22 * hour);
});
it('handles spaced AM/PM', () => {
expect(p('Today 3 pm')).toBe(TODAY_START + 15 * hour);
});
it('handles Chinese modifiers (Morning/Evening)', () => {
// "ζζ©" -> Tomorrow 8am
expect(p('ζζ©8ηΉ')).toBe(TOMORROW_START + 8 * hour);
// "ζ¨ζ" -> Yesterday 8pm (20:00)
expect(p('ζ¨ζ8ηΉ')).toBe(YESTERDAY_START + 20 * hour);
// "ε¨δΊδΈε3ηΉ" -> Last Friday 15:00
expect(p('ε¨δΊδΈε3ηΉ')).toBe(LAST_FRIDAY + 15 * hour);
// https://github.com/DIYgod/RSSHub/issues/20878
expect(p('ζ¨ε€© 23:01')).toBe(YESTERDAY_START + 23 * hour + 1 * minute);
});
it('handles 12am / 12pm edge cases', () => {
expect(p('Today 12pm')).toBe(TODAY_START + 12 * hour); // Noon
expect(p('Today 12am')).toBe(TODAY_START); // Midnight
});
});
/**
* Category 5: Edge Cases & Fallback
*/
describe('Fallback', () => {
it('passes formatting options to dayjs', () => {
const str = '05/02/2026';
const format = 'DD/MM/YYYY'; // Feb 5th
const expected = new Date('2026-02-05T00:00:00').getTime();
expect(p(str, format)).toBe(expected);
});
it('returns raw absolute dates', () => {
const raw = '2026-01-01T00:00:00';
expect(p(raw)).toBe(new Date(raw).getTime());
});
});
});
|