fix: support timezoneOffset for date from scope, #401

This commit is contained in:
Harttle
2021-10-06 15:45:41 +08:00
parent 4f6b88c154
commit fd5ef474c3
11 changed files with 132 additions and 53 deletions
+10 -4
View File
@@ -1,8 +1,10 @@
import strftime, { createDateFixedToTimezone } from '../../util/strftime'
import strftime from '../../util/strftime'
import { isString, isNumber } from '../../util/underscore'
import { FilterImpl } from '../../template/filter/filter-impl'
import { TimezoneDate } from '../../util/timezone-date'
export function date (this: FilterImpl, v: string | Date, arg: string) {
const opts = this.context.opts
let date: Date
if (v === 'now' || v === 'today') {
date = new Date()
@@ -11,15 +13,19 @@ export function date (this: FilterImpl, v: string | Date, arg: string) {
} else if (isString(v)) {
if (/^\d+$/.test(v)) {
date = new Date(+v * 1000)
} else if (this.context.opts.preserveTimezones) {
date = createDateFixedToTimezone(v)
} else if (opts.preserveTimezones) {
date = TimezoneDate.createDateFixedToTimezone(v)
} else {
date = new Date(v)
}
} else {
date = v
}
return isValidDate(date) ? strftime(date, arg) : v
if (!isValidDate(date)) return v
if (opts.hasOwnProperty('timezoneOffset')) {
date = new TimezoneDate(date, opts.timezoneOffset!)
}
return strftime(date, arg)
}
function isValidDate (date: any): date is Date {
-2
View File
@@ -6,7 +6,6 @@ import { FS } from './fs/fs'
import * as fs from './fs/node'
import { defaultOperators, Operators } from './render/operator'
import { createTrie, Trie } from './util/operator-trie'
import { timezoneOffset } from './util/strftime'
export interface LiquidOptions {
/** A directory or an array of directories from where to resolve layout and include templates, and the filename passed to `.renderFile()`. If it's an array, the files are looked up in the order they occur in the array. Defaults to `["."]` */
@@ -123,7 +122,6 @@ export const defaultOptions: NormalizedFullOptions = {
lenientIf: false,
globals: {},
keepOutputType: false,
timezoneOffset: timezoneOffset,
operators: defaultOperators,
operatorsTrie: createTrie(defaultOperators)
}
+2 -32
View File
@@ -1,7 +1,5 @@
import { changeCase, padStart, padEnd } from './underscore'
export const timezoneOffset = new Date().getTimezoneOffset()
const ISO8601_TIMEZONE_PATTERN = /([zZ]|([+-])(\d{2}):(\d{2}))$/
const rFormat = /%([-_0^#:]+)?(\d+)?([EO])?(.)/
const monthNames = [
'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August',
@@ -127,10 +125,10 @@ const formatCodes = {
y: (d: Date) => d.getFullYear().toString().substring(2, 4),
Y: (d: Date) => d.getFullYear(),
z: (d: Date, opts: FormatOptions) => {
const nOffset = Math.abs(timezoneOffset)
const nOffset = Math.abs(d.getTimezoneOffset())
const h = Math.floor(nOffset / 60)
const m = nOffset % 60
return (timezoneOffset > 0 ? '-' : '+') +
return (d.getTimezoneOffset() > 0 ? '-' : '+') +
padStart(h, 2, '0') +
(opts.flags[':'] ? ':' : '') +
padStart(m, 2, '0')
@@ -169,31 +167,3 @@ function format (d: Date, match: RegExpExecArray) {
if (flags['-']) padWidth = 0
return padStart(ret, padWidth, padChar)
}
/**
* Create a Date object fixed to it's declared Timezone. Both
* - 2021-08-06T02:29:00.000Z and
* - 2021-08-06T02:29:00.000+08:00
* will always be displayed as
* - 2021-08-06 02:29:00
* regardless timezoneOffset in JavaScript realm
*
* The implementation hack:
* Instead of calling `.getMonth()`/`.getUTCMonth()` respect to `preserveTimezones`,
* we create a different Date to trick strftime, it's both simpler and more performant.
* Given that a template is expected to be parsed fewer times than rendered.
*/
export function createDateFixedToTimezone (dateString: string) {
const m = dateString.match(ISO8601_TIMEZONE_PATTERN)
// representing a UTC datetime
if (m && m[1] === 'Z') {
return new Date(+new Date(dateString) + timezoneOffset * 60000)
}
// has a timezone specified
if (m && m[2] && m[3] && m[4]) {
const [, , sign, hours, minutes] = m
const delta = (sign === '+' ? 1 : -1) * (parseInt(hours, 10) * 60 + parseInt(minutes, 10))
return new Date(+new Date(dateString) + (timezoneOffset + delta) * 60000)
}
return new Date(dateString)
}
+53
View File
@@ -0,0 +1,53 @@
// one minute in milliseconds
const OneMinute = 60000
const hostTimezoneOffset = new Date().getTimezoneOffset()
const ISO8601_TIMEZONE_PATTERN = /([zZ]|([+-])(\d{2}):(\d{2}))$/
/**
* A date implementation with timezone info, just like Ruby date
*
* Implementation:
* - create a Date offset by it's timezone difference, avoiding overriding a bunch of methods
* - rewrite getTimezoneOffset() to trick strftime
*/
export class TimezoneDate extends Date {
private timezoneOffset?: number
constructor (init: string | number | Date, timezoneOffset: number) {
if (init instanceof TimezoneDate) return init
const diff = (hostTimezoneOffset - timezoneOffset) * OneMinute
const time = new Date(init).getTime() + diff
super(time)
this.timezoneOffset = timezoneOffset
}
getTimezoneOffset () {
return this.timezoneOffset!
}
/**
* Create a Date object fixed to it's declared Timezone. Both
* - 2021-08-06T02:29:00.000Z and
* - 2021-08-06T02:29:00.000+08:00
* will always be displayed as
* - 2021-08-06 02:29:00
* regardless timezoneOffset in JavaScript realm
*
* The implementation hack:
* Instead of calling `.getMonth()`/`.getUTCMonth()` respect to `preserveTimezones`,
* we create a different Date to trick strftime, it's both simpler and more performant.
* Given that a template is expected to be parsed fewer times than rendered.
*/
static createDateFixedToTimezone (dateString: string) {
const m = dateString.match(ISO8601_TIMEZONE_PATTERN)
// representing a UTC timestamp
if (m && m[1] === 'Z') {
return new TimezoneDate(+new Date(dateString), 0)
}
// has a timezone specified
if (m && m[2] && m[3] && m[4]) {
const [, , sign, hours, minutes] = m
const delta = (sign === '+' ? -1 : 1) * (parseInt(hours, 10) * 60 + parseInt(minutes, 10))
return new TimezoneDate(+new Date(dateString), delta)
}
return new Date(dateString)
}
}