From c16c787861bd6f2ac2721b36d2fd22b607c74ee7 Mon Sep 17 00:00:00 2001 From: wyozi Date: Mon, 7 Dec 2020 16:04:46 +0200 Subject: [PATCH] feat: parse and handle date timezone offsets --- src/builtin/filters/date.ts | 4 ++-- src/util/strftime.ts | 30 +++++++++++++++++++++++- test/integration/builtin/filters/date.ts | 14 ++++++++++- 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/builtin/filters/date.ts b/src/builtin/filters/date.ts index 96b00f4d3..a75f604e7 100644 --- a/src/builtin/filters/date.ts +++ b/src/builtin/filters/date.ts @@ -1,4 +1,4 @@ -import strftime from '../../util/strftime' +import strftime, { TimezoneDate } from '../../util/strftime' import { isString, isNumber } from '../../util/underscore' export function date (v: string | Date, arg: string) { @@ -8,7 +8,7 @@ export function date (v: string | Date, arg: string) { } else if (isNumber(v)) { date = new Date(v * 1000) } else if (isString(v)) { - date = /^\d+$/.test(v) ? new Date(+v * 1000) : new Date(v) + date = /^\d+$/.test(v) ? new Date(+v * 1000) : new TimezoneDate(v) } return isValidDate(date) ? strftime(date, arg) : v } diff --git a/src/util/strftime.ts b/src/util/strftime.ts index 895184dab..67bee422d 100644 --- a/src/util/strftime.ts +++ b/src/util/strftime.ts @@ -140,7 +140,12 @@ const formatCodes = { }; (formatCodes as any).h = formatCodes.b -export default function (d: Date, formatStr: string) { +export default function (inputDate: Date, formatStr: string) { + let d = inputDate + if (d instanceof TimezoneDate && d.timezoneOffset !== null) { + d = new Date((+d) + new Date().getTimezoneOffset() * 60 * 1000 + d.timezoneOffset * 60 * 1000) + } + let output = '' let remaining = formatStr let match @@ -168,3 +173,26 @@ function format (d: Date, match: RegExpExecArray) { if (flags['-']) padWidth = 0 return padStart(ret, padWidth, padChar) } + +export class TimezoneDate extends Date { + ISO8601_PATTERN = /^([+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([.,]\d+(?!:))?)?(\17[0-5]\d([.,]\d+)?)?([zZ]|([+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/; + ISO8601_OFFSET_PATTERN = /^([+-])(\d{2}):(\d{2})$/; + + timezoneOffset: number | null = null; + + constructor (public dateString: string) { + super(dateString) + const m = dateString.match(this.ISO8601_PATTERN) + const tzString = m && m[21] + if (tzString) { + if (tzString === 'Z') { + this.timezoneOffset = 0 + } else { + const m2 = tzString.match(this.ISO8601_OFFSET_PATTERN) + if (m2) { + this.timezoneOffset = (m2[1] === '+' ? 1 : -1) * (parseInt(m2[2], 10) * 60 + parseInt(m2[3], 10)) + } + } + } + } +} diff --git a/test/integration/builtin/filters/date.ts b/test/integration/builtin/filters/date.ts index 7edc844bc..84375c28d 100644 --- a/test/integration/builtin/filters/date.ts +++ b/test/integration/builtin/filters/date.ts @@ -12,7 +12,19 @@ describe('filters/date', function () { return test('{{ "today" | date: "%Y"}}', (new Date()).getFullYear().toString()) }) it('should parse as Date when given UTC string', function () { - return test('{{ "1991-02-22T00:00:00" | date: "%Y"}}', '1991') + return test('{{ "1991-02-22T00:00:00" | date: "%Y-%m-%dT%H:%M:%S"}}', '1991-02-22T00:00:00') + }) + it('should not change the timezone between input and output', function () { + return test('{{ "1990-12-31T23:00:00Z" | date: "%Y-%m-%dT%H:%M:%S"}}', '1990-12-31T23:00:00') + }) + it('should apply numeric offset', function () { + return test('{{ "1990-12-31T23:00:00+00:00" | date: "%Y-%m-%dT%H:%M:%S"}}', '1990-12-31T23:00:00') + }) + it('should apply numeric offset', function () { + return test('{{ "1990-12-31T23:00:00-01:00" | date: "%Y-%m-%dT%H:%M:%S"}}', '1990-12-31T23:00:00') + }) + it('should apply numeric offset', function () { + return test('{{ "1990-12-31T23:00:00+02:30" | date: "%Y-%m-%dT%H:%M:%S"}}', '1990-12-31T23:00:00') }) it('should render string as string if not valid', function () { return test('{{ "foo" | date: "%Y"}}', 'foo')