add time formats
This commit is contained in:
@@ -161,9 +161,7 @@ module Api
|
|||||||
submitter.values = Submitters::SubmitValues.maybe_remove_condition_values(submitter)
|
submitter.values = Submitters::SubmitValues.maybe_remove_condition_values(submitter)
|
||||||
end
|
end
|
||||||
|
|
||||||
submitter.values = submitter.values.transform_values do |v|
|
submitter.values = Submitters::SubmitValues.replace_current_date_placeholders(submitter)
|
||||||
v == '{{date}}' ? Time.current.in_time_zone(submitter.account.timezone).to_date.to_s : v
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
submitter
|
submitter
|
||||||
|
|||||||
@@ -527,7 +527,8 @@ export default {
|
|||||||
try {
|
try {
|
||||||
return this.formatDate(
|
return this.formatDate(
|
||||||
this.modelValue === '{{date}}' ? new Date() : new Date(this.modelValue),
|
this.modelValue === '{{date}}' ? new Date() : new Date(this.modelValue),
|
||||||
this.field.preferences?.format || (this.locale.endsWith('-US') ? 'MM/DD/YYYY' : 'DD/MM/YYYY')
|
this.field.preferences?.format || (this.locale.endsWith('-US') ? 'MM/DD/YYYY' : 'DD/MM/YYYY'),
|
||||||
|
{ withTimePlaceholders: this.modelValue === '{{date}}' }
|
||||||
)
|
)
|
||||||
} catch {
|
} catch {
|
||||||
return this.modelValue
|
return this.modelValue
|
||||||
@@ -646,36 +647,55 @@ export default {
|
|||||||
return number
|
return number
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
formatDate (date, format) {
|
formatDate (date, format, { withTimePlaceholders = false } = {}) {
|
||||||
const monthFormats = {
|
const monthFormats = { M: 'numeric', MM: '2-digit', MMM: 'short', MMMM: 'long' }
|
||||||
M: 'numeric',
|
const dayFormats = { D: 'numeric', DD: '2-digit' }
|
||||||
MM: '2-digit',
|
const yearFormats = { YYYY: 'numeric', YYY: 'numeric', YY: '2-digit' }
|
||||||
MMM: 'short',
|
const hourFormats = { H: 'numeric', HH: '2-digit', h: 'numeric', hh: '2-digit' }
|
||||||
MMMM: 'long'
|
const minuteFormats = { m: 'numeric', mm: '2-digit' }
|
||||||
}
|
const secondFormats = { s: 'numeric', ss: '2-digit' }
|
||||||
|
|
||||||
const dayFormats = {
|
const hasTime = /[HhAasz]/.test(format)
|
||||||
D: 'numeric',
|
|
||||||
DD: '2-digit'
|
|
||||||
}
|
|
||||||
|
|
||||||
const yearFormats = {
|
const opts = {
|
||||||
YYYY: 'numeric',
|
|
||||||
YYY: 'numeric',
|
|
||||||
YY: '2-digit'
|
|
||||||
}
|
|
||||||
|
|
||||||
const parts = new Intl.DateTimeFormat([], {
|
|
||||||
day: dayFormats[format.match(/D+/)],
|
day: dayFormats[format.match(/D+/)],
|
||||||
month: monthFormats[format.match(/M+/)],
|
month: monthFormats[format.match(/M+/)],
|
||||||
year: yearFormats[format.match(/Y+/)],
|
year: yearFormats[format.match(/Y+/)]
|
||||||
timeZone: 'UTC'
|
}
|
||||||
}).formatToParts(date)
|
|
||||||
|
|
||||||
return format
|
if (format.match(/H+/)) { opts.hour = hourFormats[format.match(/H+/)[0]]; opts.hour12 = false }
|
||||||
.replace(/D+/, parts.find((p) => p.type === 'day').value)
|
if (format.match(/h+/)) { opts.hour = hourFormats[format.match(/h+/)[0]]; opts.hour12 = true }
|
||||||
.replace(/M+/, parts.find((p) => p.type === 'month').value)
|
if (/[Aa]/.test(format) && opts.hour12 === undefined) opts.hour12 = true
|
||||||
.replace(/Y+/, parts.find((p) => p.type === 'year').value)
|
if (format.match(/m+/)) opts.minute = minuteFormats[format.match(/m+/)[0]]
|
||||||
|
if (format.match(/s+/)) opts.second = secondFormats[format.match(/s+/)[0]]
|
||||||
|
if (/z/.test(format)) opts.timeZoneName = 'short'
|
||||||
|
if (!hasTime) opts.timeZone = 'UTC'
|
||||||
|
|
||||||
|
const partTypes = {
|
||||||
|
M: 'month',
|
||||||
|
D: 'day',
|
||||||
|
Y: 'year',
|
||||||
|
H: 'hour',
|
||||||
|
h: 'hour',
|
||||||
|
m: 'minute',
|
||||||
|
s: 'second',
|
||||||
|
z: 'timeZoneName',
|
||||||
|
A: 'dayPeriod',
|
||||||
|
a: 'dayPeriod'
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = new Intl.DateTimeFormat([], opts).formatToParts(date)
|
||||||
|
|
||||||
|
return format.replace(/MMMM|MMM|MM|M|DD|D|YYYY|YYY|YY|HH|hh|H|h|mm|m|ss|s|A|a|z/g, (token) => {
|
||||||
|
if (withTimePlaceholders && /^(HH|hh|H|h|mm|m|ss|s|A|a)$/.test(token)) return '--'
|
||||||
|
|
||||||
|
const value = parts.find((p) => p.type === partTypes[token[0]])?.value
|
||||||
|
|
||||||
|
if (token === 'A') return (value || '').toUpperCase()
|
||||||
|
if (token === 'a') return (value || '').toLowerCase()
|
||||||
|
|
||||||
|
return value
|
||||||
|
})
|
||||||
},
|
},
|
||||||
updateMultipleSelectValue (value) {
|
updateMultipleSelectValue (value) {
|
||||||
if (this.modelValue?.includes(value)) {
|
if (this.modelValue?.includes(value)) {
|
||||||
|
|||||||
@@ -56,12 +56,18 @@
|
|||||||
class="base-input !text-2xl text-center w-full"
|
class="base-input !text-2xl text-center w-full"
|
||||||
:required="field.required"
|
:required="field.required"
|
||||||
:aria-describedby="field.description ? field.uuid + '-desc' : undefined"
|
:aria-describedby="field.description ? field.uuid + '-desc' : undefined"
|
||||||
type="date"
|
:type="inputType"
|
||||||
:name="`values[${field.uuid}]`"
|
:name="formatType === 'datetime' ? undefined : `values[${field.uuid}]`"
|
||||||
@keydown.enter="onEnter"
|
@keydown.enter="onEnter"
|
||||||
@focus="$emit('focus')"
|
@focus="$emit('focus')"
|
||||||
@paste="onPaste"
|
@paste="onPaste"
|
||||||
>
|
>
|
||||||
|
<input
|
||||||
|
v-if="formatType === 'datetime'"
|
||||||
|
type="hidden"
|
||||||
|
:name="`values[${field.uuid}]`"
|
||||||
|
:value="modelValue"
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -97,14 +103,19 @@ export default {
|
|||||||
},
|
},
|
||||||
emits: ['update:model-value', 'focus', 'submit'],
|
emits: ['update:model-value', 'focus', 'submit'],
|
||||||
computed: {
|
computed: {
|
||||||
|
formatType () {
|
||||||
|
const format = this.field.preferences?.format || ''
|
||||||
|
|
||||||
|
if (/[HhAasz]/.test(format)) return 'datetime'
|
||||||
|
if (format && !/[Dd]/.test(format)) return 'month'
|
||||||
|
|
||||||
|
return 'date'
|
||||||
|
},
|
||||||
|
inputType () {
|
||||||
|
return { datetime: 'datetime-local', month: 'month', date: 'date' }[this.formatType]
|
||||||
|
},
|
||||||
dateNowString () {
|
dateNowString () {
|
||||||
const today = new Date()
|
return this.formatDateValue(new Date())
|
||||||
|
|
||||||
const yyyy = today.getFullYear()
|
|
||||||
const mm = String(today.getMonth() + 1).padStart(2, '0')
|
|
||||||
const dd = String(today.getDate()).padStart(2, '0')
|
|
||||||
|
|
||||||
return `${yyyy}-${mm}-${dd}`
|
|
||||||
},
|
},
|
||||||
validationMin () {
|
validationMin () {
|
||||||
if (this.field.validation?.min) {
|
if (this.field.validation?.min) {
|
||||||
@@ -121,6 +132,8 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
withToday () {
|
withToday () {
|
||||||
|
if (this.formatType === 'datetime') return false
|
||||||
|
|
||||||
const todayDate = new Date().setHours(0, 0, 0, 0)
|
const todayDate = new Date().setHours(0, 0, 0, 0)
|
||||||
|
|
||||||
if (this.validationMin) {
|
if (this.validationMin) {
|
||||||
@@ -137,9 +150,25 @@ export default {
|
|||||||
},
|
},
|
||||||
value: {
|
value: {
|
||||||
set (value) {
|
set (value) {
|
||||||
|
if (this.formatType === 'datetime' && value) {
|
||||||
|
const d = new Date(value)
|
||||||
|
|
||||||
|
if (!isNaN(d)) {
|
||||||
|
this.$emit('update:model-value', d.toISOString())
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.$emit('update:model-value', value)
|
this.$emit('update:model-value', value)
|
||||||
},
|
},
|
||||||
get () {
|
get () {
|
||||||
|
if (this.formatType === 'datetime') {
|
||||||
|
const d = new Date(this.modelValue)
|
||||||
|
|
||||||
|
return isNaN(d) ? '' : this.formatDateValue(d)
|
||||||
|
}
|
||||||
|
|
||||||
return this.modelValue
|
return this.modelValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -163,20 +192,32 @@ export default {
|
|||||||
|
|
||||||
const parsedDate = new Date(pasteData)
|
const parsedDate = new Date(pasteData)
|
||||||
|
|
||||||
if (!isNaN(parsedDate)) {
|
if (isNaN(parsedDate)) return
|
||||||
const inputEl = this.$refs.input
|
|
||||||
|
|
||||||
inputEl.valueAsDate = new Date(parsedDate.getTime() - parsedDate.getTimezoneOffset() * 60000)
|
this.setInputValue(parsedDate)
|
||||||
|
|
||||||
inputEl.dispatchEvent(new Event('input', { bubbles: true }))
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
setCurrentDate () {
|
setCurrentDate () {
|
||||||
|
this.setInputValue(new Date())
|
||||||
|
},
|
||||||
|
setInputValue (date) {
|
||||||
const inputEl = this.$refs.input
|
const inputEl = this.$refs.input
|
||||||
|
|
||||||
inputEl.valueAsDate = new Date(new Date().getTime() - new Date().getTimezoneOffset() * 60000)
|
if (this.formatType === 'date') {
|
||||||
|
inputEl.valueAsDate = new Date(date.getTime() - date.getTimezoneOffset() * 60000)
|
||||||
|
} else {
|
||||||
|
inputEl.value = this.formatDateValue(date)
|
||||||
|
}
|
||||||
|
|
||||||
inputEl.dispatchEvent(new Event('input', { bubbles: true }))
|
inputEl.dispatchEvent(new Event('input', { bubbles: true }))
|
||||||
|
},
|
||||||
|
formatDateValue (date) {
|
||||||
|
const pad = (n) => String(n).padStart(2, '0')
|
||||||
|
const ymd = `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`
|
||||||
|
|
||||||
|
if (this.formatType === 'month') return ymd.slice(0, 7)
|
||||||
|
if (this.formatType === 'datetime') return `${ymd}T${pad(date.getHours())}:${pad(date.getMinutes())}`
|
||||||
|
|
||||||
|
return ymd
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,7 +137,7 @@
|
|||||||
<span
|
<span
|
||||||
v-else-if="field.default_value === '{{date}}'"
|
v-else-if="field.default_value === '{{date}}'"
|
||||||
>
|
>
|
||||||
{{ t('signing_date') }}
|
{{ /[HhAasz]/.test(field.preferences?.format || '') ? t('signing_date_and_time') : t('signing_date') }}
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
v-else-if="field.type === 'cells' && field.default_value"
|
v-else-if="field.type === 'cells' && field.default_value"
|
||||||
|
|||||||
@@ -669,6 +669,7 @@ export default {
|
|||||||
locale: this.locale,
|
locale: this.locale,
|
||||||
baseFetch: this.baseFetch,
|
baseFetch: this.baseFetch,
|
||||||
fieldTypes: this.fieldTypes,
|
fieldTypes: this.fieldTypes,
|
||||||
|
dateFormats: this.dateFormats,
|
||||||
backgroundColor: this.backgroundColor,
|
backgroundColor: this.backgroundColor,
|
||||||
withPhone: this.withPhone,
|
withPhone: this.withPhone,
|
||||||
withVerification: this.withVerification,
|
withVerification: this.withVerification,
|
||||||
@@ -823,6 +824,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
|
dateFormats: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
defaultSubmitters: {
|
defaultSubmitters: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -1030,6 +1036,8 @@ export default {
|
|||||||
return isMobileSafariIos || /android|iphone|ipad/i.test(navigator.userAgent)
|
return isMobileSafariIos || /android|iphone|ipad/i.test(navigator.userAgent)
|
||||||
},
|
},
|
||||||
defaultDateFormat () {
|
defaultDateFormat () {
|
||||||
|
if (this.dateFormats.length) return this.dateFormats[0]
|
||||||
|
|
||||||
const isUsBrowser = Intl.DateTimeFormat().resolvedOptions().locale.endsWith('-US')
|
const isUsBrowser = Intl.DateTimeFormat().resolvedOptions().locale.endsWith('-US')
|
||||||
const isUsTimezone = new Intl.DateTimeFormat('en-US', { timeZoneName: 'short' }).format(new Date()).match(/\s(?:CST|CDT|PST|PDT|EST|EDT)$/)
|
const isUsTimezone = new Intl.DateTimeFormat('en-US', { timeZoneName: 'short' }).format(new Date()).match(/\s(?:CST|CDT|PST|PDT|EST|EDT)$/)
|
||||||
|
|
||||||
|
|||||||
@@ -119,7 +119,7 @@
|
|||||||
@change="[schema.format = $event.target.value, save()]"
|
@change="[schema.format = $event.target.value, save()]"
|
||||||
>
|
>
|
||||||
<option
|
<option
|
||||||
v-for="format in dateFormats"
|
v-for="format in availableDateFormats"
|
||||||
:key="format"
|
:key="format"
|
||||||
:value="format"
|
:value="format"
|
||||||
>{{ formatDate(new Date(), format) }}</option>
|
>{{ formatDate(new Date(), format) }}</option>
|
||||||
@@ -248,7 +248,7 @@ export default {
|
|||||||
FieldType,
|
FieldType,
|
||||||
IconSettings
|
IconSettings
|
||||||
},
|
},
|
||||||
inject: ['t', 'save', 'backgroundColor'],
|
inject: ['t', 'save', 'backgroundColor', 'dateFormats'],
|
||||||
provide () {
|
provide () {
|
||||||
return {
|
return {
|
||||||
fieldTypes: ['text', 'number', 'date', 'checkbox', 'radio', 'select']
|
fieldTypes: ['text', 'number', 'date', 'checkbox', 'radio', 'select']
|
||||||
@@ -318,20 +318,23 @@ export default {
|
|||||||
'space'
|
'space'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
dateFormats () {
|
availableDateFormats () {
|
||||||
const formats = [
|
const formats = this.dateFormats.length
|
||||||
'MM/DD/YYYY',
|
? [...this.dateFormats]
|
||||||
'DD/MM/YYYY',
|
: [
|
||||||
'YYYY-MM-DD',
|
'MM/DD/YYYY',
|
||||||
'DD-MM-YYYY',
|
'DD/MM/YYYY',
|
||||||
'DD.MM.YYYY',
|
'YYYY-MM-DD',
|
||||||
'MMM D, YYYY',
|
'DD-MM-YYYY',
|
||||||
'MMMM D, YYYY',
|
'DD.MM.YYYY',
|
||||||
'D MMM YYYY',
|
'MMM D, YYYY',
|
||||||
'D MMMM YYYY'
|
'MMMM D, YYYY',
|
||||||
]
|
'MMMM YYYY',
|
||||||
|
'D MMM YYYY',
|
||||||
|
'D MMMM YYYY'
|
||||||
|
]
|
||||||
|
|
||||||
if (Intl.DateTimeFormat().resolvedOptions().timeZone?.includes('Seoul') || navigator.language?.startsWith('ko')) {
|
if (!this.dateFormats.length && (Intl.DateTimeFormat().resolvedOptions().timeZone?.includes('Seoul') || navigator.language?.startsWith('ko'))) {
|
||||||
formats.push('YYYY년 MM월 DD일')
|
formats.push('YYYY년 MM월 DD일')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,18 +404,47 @@ export default {
|
|||||||
formatDate (date, format) {
|
formatDate (date, format) {
|
||||||
const monthFormats = { M: 'numeric', MM: '2-digit', MMM: 'short', MMMM: 'long' }
|
const monthFormats = { M: 'numeric', MM: '2-digit', MMM: 'short', MMMM: 'long' }
|
||||||
const dayFormats = { D: 'numeric', DD: '2-digit' }
|
const dayFormats = { D: 'numeric', DD: '2-digit' }
|
||||||
const yearFormats = { YYYY: 'numeric', YY: '2-digit' }
|
const yearFormats = { YYYY: 'numeric', YYY: 'numeric', YY: '2-digit' }
|
||||||
|
const hourFormats = { H: 'numeric', HH: '2-digit', h: 'numeric', hh: '2-digit' }
|
||||||
|
const minuteFormats = { m: 'numeric', mm: '2-digit' }
|
||||||
|
const secondFormats = { s: 'numeric', ss: '2-digit' }
|
||||||
|
|
||||||
const parts = new Intl.DateTimeFormat([], {
|
const opts = {
|
||||||
day: dayFormats[format.match(/D+/)],
|
day: dayFormats[format.match(/D+/)],
|
||||||
month: monthFormats[format.match(/M+/)],
|
month: monthFormats[format.match(/M+/)],
|
||||||
year: yearFormats[format.match(/Y+/)]
|
year: yearFormats[format.match(/Y+/)]
|
||||||
}).formatToParts(date)
|
}
|
||||||
|
|
||||||
return format
|
if (format.match(/H+/)) { opts.hour = hourFormats[format.match(/H+/)[0]]; opts.hour12 = false }
|
||||||
.replace(/D+/, parts.find((p) => p.type === 'day').value)
|
if (format.match(/h+/)) { opts.hour = hourFormats[format.match(/h+/)[0]]; opts.hour12 = true }
|
||||||
.replace(/M+/, parts.find((p) => p.type === 'month').value)
|
if (/[Aa]/.test(format) && opts.hour12 === undefined) opts.hour12 = true
|
||||||
.replace(/Y+/, parts.find((p) => p.type === 'year').value)
|
if (format.match(/m+/)) opts.minute = minuteFormats[format.match(/m+/)[0]]
|
||||||
|
if (format.match(/s+/)) opts.second = secondFormats[format.match(/s+/)[0]]
|
||||||
|
if (/z/.test(format)) opts.timeZoneName = 'short'
|
||||||
|
|
||||||
|
const partTypes = {
|
||||||
|
M: 'month',
|
||||||
|
D: 'day',
|
||||||
|
Y: 'year',
|
||||||
|
H: 'hour',
|
||||||
|
h: 'hour',
|
||||||
|
m: 'minute',
|
||||||
|
s: 'second',
|
||||||
|
z: 'timeZoneName',
|
||||||
|
A: 'dayPeriod',
|
||||||
|
a: 'dayPeriod'
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = new Intl.DateTimeFormat([], opts).formatToParts(date)
|
||||||
|
|
||||||
|
return format.replace(/MMMM|MMM|MM|M|DD|D|YYYY|YYY|YY|HH|hh|H|h|mm|m|ss|s|A|a|z/g, (token) => {
|
||||||
|
const value = parts.find((p) => p.type === partTypes[token[0]])?.value
|
||||||
|
|
||||||
|
if (token === 'A') return (value || '').toUpperCase()
|
||||||
|
if (token === 'a') return (value || '').toLowerCase()
|
||||||
|
|
||||||
|
return value
|
||||||
|
})
|
||||||
},
|
},
|
||||||
closeDropdown () {
|
closeDropdown () {
|
||||||
this.$el.getRootNode().activeElement.blur()
|
this.$el.getRootNode().activeElement.blur()
|
||||||
|
|||||||
@@ -345,7 +345,7 @@ export default {
|
|||||||
IconMathFunction,
|
IconMathFunction,
|
||||||
FieldType
|
FieldType
|
||||||
},
|
},
|
||||||
inject: ['template', 'backgroundColor', 'selectedAreasRef', 't', 'locale', 'getFieldTypeIndex'],
|
inject: ['template', 'backgroundColor', 'selectedAreasRef', 't', 'locale', 'getFieldTypeIndex', 'dateFormats'],
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -428,7 +428,8 @@ export default {
|
|||||||
|
|
||||||
if (this.field.type === 'date') {
|
if (this.field.type === 'date') {
|
||||||
this.field.preferences.format ||=
|
this.field.preferences.format ||=
|
||||||
({ 'de-DE': 'DD.MM.YYYY' }[this.locale] || ((Intl.DateTimeFormat().resolvedOptions().locale.endsWith('-US') || new Intl.DateTimeFormat('en-US', { timeZoneName: 'short' }).format(new Date()).match(/\s(?:CST|CDT|PST|PDT|EST|EDT)$/)) ? 'MM/DD/YYYY' : 'DD/MM/YYYY'))
|
this.dateFormats[0] ||
|
||||||
|
({ 'de-DE': 'DD.MM.YYYY' }[this.locale] || ((Intl.DateTimeFormat().resolvedOptions().locale.endsWith('-US') || new Intl.DateTimeFormat('en-US', { timeZoneName: 'short' }).format(new Date()).match(/\s(?:CST|CDT|PST|PDT|EST|EDT)$/)) ? 'MM/DD/YYYY' : 'DD/MM/YYYY'))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
@@ -514,7 +514,7 @@ export default {
|
|||||||
ContextSubmenu,
|
ContextSubmenu,
|
||||||
ContextModal
|
ContextModal
|
||||||
},
|
},
|
||||||
inject: ['t', 'getFieldTypeIndex', 'template', 'withCustomFields', 'currencies'],
|
inject: ['t', 'getFieldTypeIndex', 'template', 'withCustomFields', 'currencies', 'dateFormats'],
|
||||||
props: {
|
props: {
|
||||||
contextMenu: {
|
contextMenu: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -580,7 +580,7 @@ export default {
|
|||||||
fieldNames: FieldType.computed.fieldNames,
|
fieldNames: FieldType.computed.fieldNames,
|
||||||
fieldLabels: FieldType.computed.fieldLabels,
|
fieldLabels: FieldType.computed.fieldLabels,
|
||||||
validationOptions: FieldSettings.computed.validations,
|
validationOptions: FieldSettings.computed.validations,
|
||||||
dateFormats: FieldSettings.computed.dateFormats,
|
availableDateFormats: FieldSettings.computed.availableDateFormats,
|
||||||
numberFormats: FieldSettings.computed.numberFormats,
|
numberFormats: FieldSettings.computed.numberFormats,
|
||||||
prefillableFieldTypes: FieldSettings.computed.prefillableFieldTypes,
|
prefillableFieldTypes: FieldSettings.computed.prefillableFieldTypes,
|
||||||
verificationMethods: FieldSettings.computed.verificationMethods,
|
verificationMethods: FieldSettings.computed.verificationMethods,
|
||||||
@@ -686,7 +686,7 @@ export default {
|
|||||||
},
|
},
|
||||||
formatOptions () {
|
formatOptions () {
|
||||||
switch (this.field.type) {
|
switch (this.field.type) {
|
||||||
case 'date': return this.dateFormats.map(f => ({ value: f, label: this.formatDate(new Date(), f) }))
|
case 'date': return this.availableDateFormats.map(f => ({ value: f, label: this.formatDate(new Date(), f) }))
|
||||||
case 'number': return this.numberFormats.map(f => ({ value: f, label: this.formatNumber(123456789.567, f) }))
|
case 'number': return this.numberFormats.map(f => ({ value: f, label: this.formatNumber(123456789.567, f) }))
|
||||||
case 'signature': return this.signatureFormats.map(f => ({ value: f, label: this.t(f) }))
|
case 'signature': return this.signatureFormats.map(f => ({ value: f, label: this.t(f) }))
|
||||||
default: return []
|
default: return []
|
||||||
|
|||||||
@@ -290,7 +290,7 @@
|
|||||||
@change="$emit('save')"
|
@change="$emit('save')"
|
||||||
>
|
>
|
||||||
<option
|
<option
|
||||||
v-for="format in dateFormats"
|
v-for="format in availableDateFormats"
|
||||||
:key="format"
|
:key="format"
|
||||||
:value="format"
|
:value="format"
|
||||||
>
|
>
|
||||||
@@ -610,7 +610,7 @@ export default {
|
|||||||
IconTypography,
|
IconTypography,
|
||||||
IconX
|
IconX
|
||||||
},
|
},
|
||||||
inject: ['template', 't'],
|
inject: ['template', 't', 'dateFormats'],
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -701,20 +701,23 @@ export default {
|
|||||||
'space'
|
'space'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
dateFormats () {
|
availableDateFormats () {
|
||||||
const formats = [
|
const formats = this.dateFormats.length
|
||||||
'MM/DD/YYYY',
|
? [...this.dateFormats]
|
||||||
'DD/MM/YYYY',
|
: [
|
||||||
'YYYY-MM-DD',
|
'MM/DD/YYYY',
|
||||||
'DD-MM-YYYY',
|
'DD/MM/YYYY',
|
||||||
'DD.MM.YYYY',
|
'YYYY-MM-DD',
|
||||||
'MMM D, YYYY',
|
'DD-MM-YYYY',
|
||||||
'MMMM D, YYYY',
|
'DD.MM.YYYY',
|
||||||
'D MMM YYYY',
|
'MMM D, YYYY',
|
||||||
'D MMMM YYYY'
|
'MMMM D, YYYY',
|
||||||
]
|
'MMMM YYYY',
|
||||||
|
'D MMM YYYY',
|
||||||
|
'D MMMM YYYY'
|
||||||
|
]
|
||||||
|
|
||||||
if (Intl.DateTimeFormat().resolvedOptions().timeZone?.includes('Seoul') || navigator.language?.startsWith('ko')) {
|
if (!this.dateFormats.length && (Intl.DateTimeFormat().resolvedOptions().timeZone?.includes('Seoul') || navigator.language?.startsWith('ko'))) {
|
||||||
formats.push('YYYY년 MM월 DD일')
|
formats.push('YYYY년 MM월 DD일')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -819,33 +822,49 @@ export default {
|
|||||||
return pattern?.match(/^\.{(?<min>\d+),(?<max>\d+)?}$/)?.groups || null
|
return pattern?.match(/^\.{(?<min>\d+),(?<max>\d+)?}$/)?.groups || null
|
||||||
},
|
},
|
||||||
formatDate (date, format) {
|
formatDate (date, format) {
|
||||||
const monthFormats = {
|
const monthFormats = { M: 'numeric', MM: '2-digit', MMM: 'short', MMMM: 'long' }
|
||||||
M: 'numeric',
|
const dayFormats = { D: 'numeric', DD: '2-digit' }
|
||||||
MM: '2-digit',
|
const yearFormats = { YYYY: 'numeric', YYY: 'numeric', YY: '2-digit' }
|
||||||
MMM: 'short',
|
const hourFormats = { H: 'numeric', HH: '2-digit', h: 'numeric', hh: '2-digit' }
|
||||||
MMMM: 'long'
|
const minuteFormats = { m: 'numeric', mm: '2-digit' }
|
||||||
}
|
const secondFormats = { s: 'numeric', ss: '2-digit' }
|
||||||
|
|
||||||
const dayFormats = {
|
const opts = {
|
||||||
D: 'numeric',
|
|
||||||
DD: '2-digit'
|
|
||||||
}
|
|
||||||
|
|
||||||
const yearFormats = {
|
|
||||||
YYYY: 'numeric',
|
|
||||||
YY: '2-digit'
|
|
||||||
}
|
|
||||||
|
|
||||||
const parts = new Intl.DateTimeFormat([], {
|
|
||||||
day: dayFormats[format.match(/D+/)],
|
day: dayFormats[format.match(/D+/)],
|
||||||
month: monthFormats[format.match(/M+/)],
|
month: monthFormats[format.match(/M+/)],
|
||||||
year: yearFormats[format.match(/Y+/)]
|
year: yearFormats[format.match(/Y+/)]
|
||||||
}).formatToParts(date)
|
}
|
||||||
|
|
||||||
return format
|
if (format.match(/H+/)) { opts.hour = hourFormats[format.match(/H+/)[0]]; opts.hour12 = false }
|
||||||
.replace(/D+/, parts.find((p) => p.type === 'day').value)
|
if (format.match(/h+/)) { opts.hour = hourFormats[format.match(/h+/)[0]]; opts.hour12 = true }
|
||||||
.replace(/M+/, parts.find((p) => p.type === 'month').value)
|
if (/[Aa]/.test(format) && opts.hour12 === undefined) opts.hour12 = true
|
||||||
.replace(/Y+/, parts.find((p) => p.type === 'year').value)
|
if (format.match(/m+/)) opts.minute = minuteFormats[format.match(/m+/)[0]]
|
||||||
|
if (format.match(/s+/)) opts.second = secondFormats[format.match(/s+/)[0]]
|
||||||
|
if (/z/.test(format)) opts.timeZoneName = 'short'
|
||||||
|
|
||||||
|
const partTypes = {
|
||||||
|
M: 'month',
|
||||||
|
D: 'day',
|
||||||
|
Y: 'year',
|
||||||
|
H: 'hour',
|
||||||
|
h: 'hour',
|
||||||
|
m: 'minute',
|
||||||
|
s: 'second',
|
||||||
|
z: 'timeZoneName',
|
||||||
|
A: 'dayPeriod',
|
||||||
|
a: 'dayPeriod'
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = new Intl.DateTimeFormat([], opts).formatToParts(date)
|
||||||
|
|
||||||
|
return format.replace(/MMMM|MMM|MM|M|DD|D|YYYY|YYY|YY|HH|hh|H|h|mm|m|ss|s|A|a|z/g, (token) => {
|
||||||
|
const value = parts.find((p) => p.type === partTypes[token[0]])?.value
|
||||||
|
|
||||||
|
if (token === 'A') return (value || '').toUpperCase()
|
||||||
|
if (token === 'a') return (value || '').toLowerCase()
|
||||||
|
|
||||||
|
return value
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ const en = {
|
|||||||
sign_yourself: 'Sign Yourself',
|
sign_yourself: 'Sign Yourself',
|
||||||
set_signing_date: 'Set signing date',
|
set_signing_date: 'Set signing date',
|
||||||
signing_date: 'Signing Date',
|
signing_date: 'Signing Date',
|
||||||
|
signing_date_and_time: 'Signing Date and Time',
|
||||||
send: 'Send',
|
send: 'Send',
|
||||||
remove: 'Remove',
|
remove: 'Remove',
|
||||||
edit: 'Edit',
|
edit: 'Edit',
|
||||||
@@ -271,6 +272,7 @@ const es = {
|
|||||||
with_logo: 'Con logotipo',
|
with_logo: 'Con logotipo',
|
||||||
description: 'Descripción',
|
description: 'Descripción',
|
||||||
signing_date: 'Fecha de Firma',
|
signing_date: 'Fecha de Firma',
|
||||||
|
signing_date_and_time: 'Fecha y Hora de Firma',
|
||||||
display_title: 'Título de visualización',
|
display_title: 'Título de visualización',
|
||||||
unchecked: 'No marcado',
|
unchecked: 'No marcado',
|
||||||
price: 'Precio',
|
price: 'Precio',
|
||||||
@@ -508,6 +510,7 @@ const it = {
|
|||||||
sign_yourself: 'Firma te stesso',
|
sign_yourself: 'Firma te stesso',
|
||||||
set_signing_date: 'Imposta data di firma',
|
set_signing_date: 'Imposta data di firma',
|
||||||
signing_date: 'Data di firma',
|
signing_date: 'Data di firma',
|
||||||
|
signing_date_and_time: 'Data e ora di firma',
|
||||||
send: 'Invia',
|
send: 'Invia',
|
||||||
remove: 'Rimuovi',
|
remove: 'Rimuovi',
|
||||||
edit: 'Modifica',
|
edit: 'Modifica',
|
||||||
@@ -710,6 +713,7 @@ const pt = {
|
|||||||
description: 'Descrição',
|
description: 'Descrição',
|
||||||
display_title: 'Título de exibição',
|
display_title: 'Título de exibição',
|
||||||
signing_date: 'Data da Assinatura',
|
signing_date: 'Data da Assinatura',
|
||||||
|
signing_date_and_time: 'Data e Hora da Assinatura',
|
||||||
unchecked: 'Não marcado',
|
unchecked: 'Não marcado',
|
||||||
price: 'Preço',
|
price: 'Preço',
|
||||||
equal: 'Igual',
|
equal: 'Igual',
|
||||||
@@ -946,6 +950,7 @@ const fr = {
|
|||||||
sign_yourself: 'Signer vous-même',
|
sign_yourself: 'Signer vous-même',
|
||||||
set_signing_date: 'Définir la date de signature',
|
set_signing_date: 'Définir la date de signature',
|
||||||
signing_date: 'Date de signature',
|
signing_date: 'Date de signature',
|
||||||
|
signing_date_and_time: 'Date et heure de signature',
|
||||||
send: 'Envoyer',
|
send: 'Envoyer',
|
||||||
remove: 'Supprimer',
|
remove: 'Supprimer',
|
||||||
edit: 'Modifier',
|
edit: 'Modifier',
|
||||||
@@ -1165,6 +1170,7 @@ const de = {
|
|||||||
sign_yourself: 'Selbst unterschreiben',
|
sign_yourself: 'Selbst unterschreiben',
|
||||||
set_signing_date: 'Unterzeichnungsdatum festlegen',
|
set_signing_date: 'Unterzeichnungsdatum festlegen',
|
||||||
signing_date: 'Unterzeichnungsdatum',
|
signing_date: 'Unterzeichnungsdatum',
|
||||||
|
signing_date_and_time: 'Unterzeichnungsdatum und -uhrzeit',
|
||||||
send: 'Senden',
|
send: 'Senden',
|
||||||
remove: 'Entfernen',
|
remove: 'Entfernen',
|
||||||
edit: 'Bearbeiten',
|
edit: 'Bearbeiten',
|
||||||
@@ -1384,6 +1390,7 @@ const nl = {
|
|||||||
sign_yourself: 'Zelf ondertekenen',
|
sign_yourself: 'Zelf ondertekenen',
|
||||||
set_signing_date: 'Ondertekeningsdatum instellen',
|
set_signing_date: 'Ondertekeningsdatum instellen',
|
||||||
signing_date: 'Ondertekeningsdatum',
|
signing_date: 'Ondertekeningsdatum',
|
||||||
|
signing_date_and_time: 'Ondertekeningsdatum en -tijd',
|
||||||
send: 'Verzenden',
|
send: 'Verzenden',
|
||||||
remove: 'Verwijderen',
|
remove: 'Verwijderen',
|
||||||
edit: 'Bewerken',
|
edit: 'Bewerken',
|
||||||
|
|||||||
@@ -73,8 +73,8 @@
|
|||||||
<% elsif field['type'] == 'date' %>
|
<% elsif field['type'] == 'date' %>
|
||||||
<autosize-field></autosize-field>
|
<autosize-field></autosize-field>
|
||||||
<div class="flex w-full px-0.5 <%= valign == 'top' ? 'items-start' : (valign == 'bottom' ? 'items-end' : 'items-center') %>">
|
<div class="flex w-full px-0.5 <%= valign == 'top' ? 'items-start' : (valign == 'bottom' ? 'items-end' : 'items-center') %>">
|
||||||
<% value = Time.current.in_time_zone(local_assigns[:timezone]).to_date.to_s if value == '{{date}}' %>
|
<% tz = local_assigns[:with_submitter_timezone] ? (submitter.timezone.presence || local_assigns[:timezone]) : local_assigns[:timezone] %>
|
||||||
<div class="w-full"><%= TimeUtils.format_date_string(value, field.dig('preferences', 'format'), local_assigns[:locale]) %></div>
|
<div class="w-full"><%= value == '{{date}}' ? TimeUtils.format_date_preview(field.dig('preferences', 'format'), local_assigns[:locale], tz) : TimeUtils.format_date_string(value, field.dig('preferences', 'format'), local_assigns[:locale], timezone: tz) %></div>
|
||||||
</div>
|
</div>
|
||||||
<% elsif field['type'] == 'number' %>
|
<% elsif field['type'] == 'number' %>
|
||||||
<autosize-field></autosize-field>
|
<autosize-field></autosize-field>
|
||||||
|
|||||||
@@ -281,7 +281,7 @@
|
|||||||
<% if field['type'] == 'number' %>
|
<% if field['type'] == 'number' %>
|
||||||
<% value = NumberUtils.format_number(value, field.dig('preferences', 'format')) %>
|
<% value = NumberUtils.format_number(value, field.dig('preferences', 'format')) %>
|
||||||
<% elsif field['type'] == 'date' %>
|
<% elsif field['type'] == 'date' %>
|
||||||
<% value = TimeUtils.format_date_string(value, field.dig('preferences', 'format'), @submission.account.locale) %>
|
<% value = TimeUtils.format_date_string(value, field.dig('preferences', 'format'), @submission.account.locale, timezone: (with_submitter_timezone ? (submitter.timezone.presence || @submission.account.timezone) : @submission.account.timezone)) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if (mask = field.dig('preferences', 'mask').presence) %>
|
<% if (mask = field.dig('preferences', 'mask').presence) %>
|
||||||
<% if signed_in? && can?(:read, @submission) %>
|
<% if signed_in? && can?(:read, @submission) %>
|
||||||
|
|||||||
@@ -410,9 +410,7 @@ module Submissions
|
|||||||
submitter.values = Submitters::SubmitValues.maybe_remove_condition_values(submitter)
|
submitter.values = Submitters::SubmitValues.maybe_remove_condition_values(submitter)
|
||||||
end
|
end
|
||||||
|
|
||||||
submitter.values = submitter.values.transform_values do |v|
|
submitter.values = Submitters::SubmitValues.replace_current_date_placeholders(submitter)
|
||||||
v == '{{date}}' ? Time.current.in_time_zone(submitter.submission.account.timezone).to_date.to_s : v
|
|
||||||
end
|
|
||||||
|
|
||||||
submitter
|
submitter
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -415,7 +415,8 @@ module Submissions
|
|||||||
composer.formatted_text_box([{ text: value.to_s.titleize }], padding: [0, 0, 10, 0])
|
composer.formatted_text_box([{ text: value.to_s.titleize }], padding: [0, 0, 10, 0])
|
||||||
else
|
else
|
||||||
if field['type'] == 'date'
|
if field['type'] == 'date'
|
||||||
value = TimeUtils.format_date_string(value, field.dig('preferences', 'format'), account.locale)
|
value = TimeUtils.format_date_string(value, field.dig('preferences', 'format'), account.locale,
|
||||||
|
timezone:)
|
||||||
end
|
end
|
||||||
|
|
||||||
value = NumberUtils.format_number(value, field.dig('preferences', 'format')) if field['type'] == 'number'
|
value = NumberUtils.format_number(value, field.dig('preferences', 'format')) if field['type'] == 'number'
|
||||||
|
|||||||
@@ -652,7 +652,10 @@ module Submissions
|
|||||||
end
|
end
|
||||||
else
|
else
|
||||||
if field['type'] == 'date'
|
if field['type'] == 'date'
|
||||||
value = TimeUtils.format_date_string(value, field.dig('preferences', 'format'), locale)
|
timezone = submitter.account.timezone
|
||||||
|
timezone = submitter.timezone || submitter.account.timezone if with_submitter_timezone
|
||||||
|
|
||||||
|
value = TimeUtils.format_date_string(value, field.dig('preferences', 'format'), locale, timezone:)
|
||||||
end
|
end
|
||||||
|
|
||||||
value = NumberUtils.format_number(value, field.dig('preferences', 'format')) if field['type'] == 'number'
|
value = NumberUtils.format_number(value, field.dig('preferences', 'format')) if field['type'] == 'number'
|
||||||
|
|||||||
@@ -103,17 +103,43 @@ module Submitters
|
|||||||
end
|
end
|
||||||
|
|
||||||
def normalize_date(field, value)
|
def normalize_date(field, value)
|
||||||
if value.is_a?(Integer)
|
format = field.dig('preferences', 'format')
|
||||||
|
|
||||||
|
if TimeUtils.format_with_time?(format)
|
||||||
|
normalize_date_time(value, format)
|
||||||
|
elsif TimeUtils.month_only_format?(format)
|
||||||
|
normalize_date_month(value, format)
|
||||||
|
elsif value.is_a?(Integer)
|
||||||
Time.zone.at(value.to_s.first(10).to_i).to_date.to_s
|
Time.zone.at(value.to_s.first(10).to_i).to_date.to_s
|
||||||
elsif value.gsub(/\w/, '0') == field.dig('preferences', 'format').to_s.gsub(/\w/, '0')
|
elsif value.gsub(/\w/, '0') == format.to_s.gsub(/\w/, '0')
|
||||||
TimeUtils.parse_date_string(value, field.dig('preferences', 'format')).to_s
|
TimeUtils.parse_date_string(value, format).to_s
|
||||||
else
|
else
|
||||||
Date.parse(value).to_s
|
Date.parse(value).to_s
|
||||||
end
|
end
|
||||||
rescue Date::Error
|
rescue ArgumentError
|
||||||
value
|
value
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def normalize_date_time(value, format)
|
||||||
|
if value.is_a?(Integer)
|
||||||
|
Time.zone.at(value.to_s.first(10).to_i).utc.iso8601
|
||||||
|
elsif value.to_s.match?(/T\d{2}:\d{2}/)
|
||||||
|
Time.iso8601(value).utc.iso8601
|
||||||
|
else
|
||||||
|
TimeUtils.parse_date_string(value, format).utc.iso8601
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def normalize_date_month(value, format)
|
||||||
|
if value.is_a?(Integer)
|
||||||
|
Time.zone.at(value.to_s.first(10).to_i).strftime('%Y-%m')
|
||||||
|
elsif value.to_s.match?(/\A\d{4}-\d{2}\z/)
|
||||||
|
value
|
||||||
|
else
|
||||||
|
TimeUtils.parse_date_string(value, format).strftime('%Y-%m')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def fetch_fields(template, submitter_name: nil, for_submitter: nil)
|
def fetch_fields(template, submitter_name: nil, for_submitter: nil)
|
||||||
if submitter_name && !for_submitter
|
if submitter_name && !for_submitter
|
||||||
submitter =
|
submitter =
|
||||||
|
|||||||
@@ -86,9 +86,7 @@ module Submitters
|
|||||||
submitter.values = maybe_remove_condition_values(submitter, required_field_uuids_acc:)
|
submitter.values = maybe_remove_condition_values(submitter, required_field_uuids_acc:)
|
||||||
end
|
end
|
||||||
|
|
||||||
submitter.values = submitter.values.transform_values do |v|
|
submitter.values = replace_current_date_placeholders(submitter)
|
||||||
v == '{{date}}' ? Time.current.in_time_zone(submitter.account.timezone).to_date.to_s : v
|
|
||||||
end
|
|
||||||
|
|
||||||
required_field_uuids_acc.each do |uuid|
|
required_field_uuids_acc.each do |uuid|
|
||||||
next if submitter.values[uuid].present?
|
next if submitter.values[uuid].present?
|
||||||
@@ -194,7 +192,7 @@ module Submitters
|
|||||||
|
|
||||||
next if value.blank?
|
next if value.blank?
|
||||||
|
|
||||||
acc[field['uuid']] = template_default_value_for_submitter(value, submitter, with_time: true)
|
acc[field['uuid']] = template_default_value_for_submitter(value, submitter, field:, with_time: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
default_values.compact_blank.merge(submitter.values)
|
default_values.compact_blank.merge(submitter.values)
|
||||||
@@ -248,7 +246,20 @@ module Submitters
|
|||||||
0
|
0
|
||||||
end
|
end
|
||||||
|
|
||||||
def template_default_value_for_submitter(value, submitter, with_time: false)
|
def replace_current_date_placeholders(submitter)
|
||||||
|
submitter.values.each_with_object({}) do |(uuid, v), acc|
|
||||||
|
acc[uuid] =
|
||||||
|
if v == '{{date}}'
|
||||||
|
field = submitter.submission.fields_uuid_index[uuid]
|
||||||
|
|
||||||
|
TimeUtils.current_date_value(field&.dig('preferences', 'format'), submitter.account.timezone)
|
||||||
|
else
|
||||||
|
v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def template_default_value_for_submitter(value, submitter, with_time: false, field: nil)
|
||||||
return if value.blank?
|
return if value.blank?
|
||||||
return if submitter.blank?
|
return if submitter.blank?
|
||||||
|
|
||||||
@@ -257,7 +268,8 @@ module Submitters
|
|||||||
replace_default_variables(value,
|
replace_default_variables(value,
|
||||||
submitter.attributes.merge('role' => role),
|
submitter.attributes.merge('role' => role),
|
||||||
submitter.submission,
|
submitter.submission,
|
||||||
with_time:)
|
with_time:,
|
||||||
|
field:)
|
||||||
end
|
end
|
||||||
|
|
||||||
def maybe_remove_condition_values(submitter, required_field_uuids_acc: nil)
|
def maybe_remove_condition_values(submitter, required_field_uuids_acc: nil)
|
||||||
@@ -394,7 +406,7 @@ module Submitters
|
|||||||
end
|
end
|
||||||
# rubocop:enable Metrics
|
# rubocop:enable Metrics
|
||||||
|
|
||||||
def replace_default_variables(value, attrs, submission, with_time: false)
|
def replace_default_variables(value, attrs, submission, with_time: false, field: nil)
|
||||||
return value if value.in?([true, false]) || value.is_a?(Numeric) || value.is_a?(Array)
|
return value if value.in?([true, false]) || value.is_a?(Numeric) || value.is_a?(Array)
|
||||||
return if value.blank?
|
return if value.blank?
|
||||||
|
|
||||||
@@ -412,7 +424,7 @@ module Submitters
|
|||||||
when 'hour', 'minute', 'day', 'month', 'year'
|
when 'hour', 'minute', 'day', 'month', 'year'
|
||||||
with_time ? Time.current.in_time_zone(submission.account.timezone).strftime(STRFTIME_MAP[key]) : e
|
with_time ? Time.current.in_time_zone(submission.account.timezone).strftime(STRFTIME_MAP[key]) : e
|
||||||
when 'date'
|
when 'date'
|
||||||
with_time ? Time.current.in_time_zone(submission.account.timezone).to_date.to_s : e
|
with_time ? TimeUtils.current_date_value(field&.dig('preferences', 'format'), submission.account.timezone) : e
|
||||||
when 'role', 'email', 'phone', 'name'
|
when 'role', 'email', 'phone', 'name'
|
||||||
attrs[key] || e
|
attrs[key] || e
|
||||||
else
|
else
|
||||||
|
|||||||
+88
-14
@@ -19,6 +19,47 @@ module TimeUtils
|
|||||||
'YY' => '%y'
|
'YY' => '%y'
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
|
HOUR_FORMATS = {
|
||||||
|
'H' => '%-H',
|
||||||
|
'HH' => '%H',
|
||||||
|
'h' => '%-I',
|
||||||
|
'hh' => '%I'
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
MINUTE_FORMATS = {
|
||||||
|
'm' => '%-M',
|
||||||
|
'mm' => '%M'
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
SECOND_FORMATS = {
|
||||||
|
's' => '%-S',
|
||||||
|
'ss' => '%S'
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
AMPM_FORMATS = {
|
||||||
|
'A' => '%p',
|
||||||
|
'a' => '%P'
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
TIMEZONE_FORMATS = {
|
||||||
|
'z' => '%Z'
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
TIME_FORMATS = HOUR_FORMATS.merge(MINUTE_FORMATS)
|
||||||
|
.merge(SECOND_FORMATS)
|
||||||
|
.merge(AMPM_FORMATS)
|
||||||
|
.freeze
|
||||||
|
|
||||||
|
ALL_FORMATS = MONTH_FORMATS.merge(DAY_FORMATS)
|
||||||
|
.merge(YEAR_FORMATS)
|
||||||
|
.merge(TIME_FORMATS)
|
||||||
|
.merge(TIMEZONE_FORMATS)
|
||||||
|
.freeze
|
||||||
|
|
||||||
|
TOKEN_REGEX = /MMMM|MMM|MM|M|DD|D|YYYY|YYY|YY|HH|hh|H|h|mm|m|ss|s|A|a|z/
|
||||||
|
|
||||||
|
MONTH_ONLY_VALUE_REGEX = /\A\d{4}-\d{2}\z/
|
||||||
|
|
||||||
DEFAULT_DATE_FORMAT_US = 'MM/DD/YYYY'
|
DEFAULT_DATE_FORMAT_US = 'MM/DD/YYYY'
|
||||||
DEFAULT_DATE_FORMAT = 'DD/MM/YYYY'
|
DEFAULT_DATE_FORMAT = 'DD/MM/YYYY'
|
||||||
|
|
||||||
@@ -42,27 +83,60 @@ module TimeUtils
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse_date_string(string, pattern)
|
def format_with_time?(format)
|
||||||
pattern = pattern.sub(/Y+/, YEAR_FORMATS)
|
format.to_s.match?(/[HhAasz]/)
|
||||||
.sub(/M+/, MONTH_FORMATS)
|
|
||||||
.sub(/D+/, DAY_FORMATS)
|
|
||||||
|
|
||||||
Date.strptime(string, pattern)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def format_date_string(string, format, locale)
|
def month_only_format?(format)
|
||||||
date = Date.parse(string.to_s)
|
format.to_s.present? && !format.to_s.match?(/[DdHhAasz]/)
|
||||||
|
end
|
||||||
|
|
||||||
format = format.upcase if format
|
def format_date_preview(format, locale, timezone)
|
||||||
|
return '' if format.blank?
|
||||||
|
|
||||||
|
format = format.upcase unless format_with_time?(format)
|
||||||
|
preview_pattern = format.gsub(TOKEN_REGEX) { |token| TIME_FORMATS.key?(token) ? '--' : ALL_FORMATS[token] }
|
||||||
|
|
||||||
|
I18n.l(Time.current.in_time_zone(timezone.presence || Time.zone.name), format: preview_pattern, locale:)
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_date_value(format, timezone)
|
||||||
|
tz = timezone.presence || Time.zone.name
|
||||||
|
|
||||||
|
if format_with_time?(format)
|
||||||
|
Time.current.utc.iso8601
|
||||||
|
elsif month_only_format?(format)
|
||||||
|
Time.current.in_time_zone(tz).strftime('%Y-%m')
|
||||||
|
else
|
||||||
|
Time.current.in_time_zone(tz).to_date.to_s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_date_string(string, pattern)
|
||||||
|
with_time = format_with_time?(pattern)
|
||||||
|
pattern = pattern.upcase unless with_time
|
||||||
|
pattern = pattern.gsub(TOKEN_REGEX, ALL_FORMATS)
|
||||||
|
|
||||||
|
with_time ? Time.zone.strptime(string, pattern) : Date.strptime(string, pattern)
|
||||||
|
end
|
||||||
|
|
||||||
|
def format_date_string(string, format, locale, timezone: nil)
|
||||||
|
format = format.upcase if format && !format_with_time?(format)
|
||||||
format ||= locale.to_s.ends_with?('US') ? DEFAULT_DATE_FORMAT_US : DEFAULT_DATE_FORMAT
|
format ||= locale.to_s.ends_with?('US') ? DEFAULT_DATE_FORMAT_US : DEFAULT_DATE_FORMAT
|
||||||
|
|
||||||
i18n_format = format.sub(/D+/) { DAY_FORMATS[format[/D+/]] }
|
date =
|
||||||
.sub(/M+/) { MONTH_FORMATS[format[/M+/]] }
|
if format_with_time?(format)
|
||||||
.sub(/Y+/) { YEAR_FORMATS[format[/Y+/]] }
|
Time.iso8601(string.to_s).in_time_zone(timezone.presence || Time.zone.name)
|
||||||
|
elsif string.to_s.match?(MONTH_ONLY_VALUE_REGEX)
|
||||||
|
year, month = string.to_s.split('-').map(&:to_i)
|
||||||
|
|
||||||
I18n.l(date, format: i18n_format, locale:)
|
Date.new(year, month, 1)
|
||||||
rescue Date::Error
|
else
|
||||||
|
Date.parse(string.to_s)
|
||||||
|
end
|
||||||
|
|
||||||
|
I18n.l(date, format: format.gsub(TOKEN_REGEX, ALL_FORMATS), locale:)
|
||||||
|
rescue ArgumentError
|
||||||
string
|
string
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user