diff --git a/app/javascript/submission_form/area.vue b/app/javascript/submission_form/area.vue
index 9145900d8..50f32288e 100644
--- a/app/javascript/submission_form/area.vue
+++ b/app/javascript/submission_form/area.vue
@@ -273,9 +273,15 @@ export default {
return null
}
},
+ locale () {
+ return Intl.DateTimeFormat().resolvedOptions()?.locale
+ },
formattedDate () {
if (this.field.type === 'date' && this.modelValue) {
- return new Intl.DateTimeFormat([], { year: 'numeric', month: 'long', day: 'numeric', timeZone: 'UTC' }).format(new Date(this.modelValue))
+ return this.formatDate(
+ new Date(this.modelValue),
+ this.field.preferences?.format || (this.locale.endsWith('-US') ? 'MM/DD/YYYY' : 'DD/MM/YYYY')
+ )
} else {
return ''
}
@@ -315,6 +321,36 @@ export default {
}
},
methods: {
+ formatDate (date, format) {
+ const monthFormats = {
+ M: 'numeric',
+ MM: '2-digit',
+ MMM: 'short',
+ MMMM: 'long'
+ }
+
+ const dayFormats = {
+ D: 'numeric',
+ DD: '2-digit'
+ }
+
+ const yearFormats = {
+ YYYY: 'numeric',
+ YY: '2-digit'
+ }
+
+ const parts = new Intl.DateTimeFormat([], {
+ day: dayFormats[format.match(/D+/)],
+ month: monthFormats[format.match(/M+/)],
+ year: yearFormats[format.match(/Y+/)],
+ timeZone: 'UTC'
+ }).formatToParts(date)
+
+ return format
+ .replace(/D+/, parts.find((p) => p.type === 'day').value)
+ .replace(/M+/, parts.find((p) => p.type === 'month').value)
+ .replace(/Y+/, parts.find((p) => p.type === 'year').value)
+ },
updateMultipleSelectValue (value) {
if (this.modelValue?.includes(value)) {
const newValue = [...this.modelValue]
diff --git a/app/javascript/template_builder/builder.vue b/app/javascript/template_builder/builder.vue
index 351bec90f..c0be64f2d 100644
--- a/app/javascript/template_builder/builder.vue
+++ b/app/javascript/template_builder/builder.vue
@@ -474,6 +474,12 @@ export default {
field.options = [{ value: '', uuid: v4() }]
}
+ if (type === 'date') {
+ field.preferences = {
+ format: Intl.DateTimeFormat().resolvedOptions().locale.endsWith('-US') ? 'MM/DD/YYYY' : 'DD/MM/YYYY'
+ }
+ }
+
this.drawField = field
this.drawOption = null
},
@@ -650,6 +656,12 @@ export default {
field.options = [{ value: '', uuid: v4() }]
}
+ if (field.type === 'date') {
+ field.preferences = {
+ format: Intl.DateTimeFormat().resolvedOptions().locale.endsWith('-US') ? 'MM/DD/YYYY' : 'DD/MM/YYYY'
+ }
+ }
+
const fieldArea = {
x: (area.x - 6) / area.maskW,
y: area.y / area.maskH,
diff --git a/app/javascript/template_builder/field.vue b/app/javascript/template_builder/field.vue
index c5285095d..3fb00e76c 100644
--- a/app/javascript/template_builder/field.vue
+++ b/app/javascript/template_builder/field.vue
@@ -111,6 +111,33 @@
Default value
+
+
+
+
p.type === 'day').value)
+ .replace(/M+/, parts.find((p) => p.type === 'month').value)
+ .replace(/Y+/, parts.find((p) => p.type === 'year').value)
+ },
copyToAllPages (field) {
const areaString = JSON.stringify(field.areas[0])
diff --git a/app/javascript/template_builder/fields.vue b/app/javascript/template_builder/fields.vue
index c4e692fd1..b3f5e91c5 100644
--- a/app/javascript/template_builder/fields.vue
+++ b/app/javascript/template_builder/fields.vue
@@ -261,6 +261,12 @@ export default {
field.options = [{ value: '', uuid: v4() }]
}
+ if (type === 'date') {
+ field.preferences = {
+ format: Intl.DateTimeFormat().resolvedOptions().locale.endsWith('-US') ? 'MM/DD/YYYY' : 'DD/MM/YYYY'
+ }
+ }
+
this.fields.push(field)
if (['signature', 'initials', 'cells'].includes(type)) {
diff --git a/app/views/submissions/_value.html.erb b/app/views/submissions/_value.html.erb
index 52a030c8e..900b4e691 100644
--- a/app/views/submissions/_value.html.erb
+++ b/app/views/submissions/_value.html.erb
@@ -34,7 +34,7 @@
<% elsif field['type'] == 'date' %>
- <%= l(Date.parse(value), format: :long, locale: local_assigns[:locale]) %>
+ <%= TimeUtils.format_date_string(value, field.dig('preferences', 'format'), local_assigns[:locale]) %>
<% else %>
diff --git a/app/views/submissions/show.html.erb b/app/views/submissions/show.html.erb
index 03ead8ece..d390d633d 100644
--- a/app/views/submissions/show.html.erb
+++ b/app/views/submissions/show.html.erb
@@ -162,7 +162,7 @@
<% elsif field['type'] == 'checkbox' %>
<%= svg_icon('check', class: 'w-6 h-6') %>
<% elsif field['type'] == 'date' %>
- <%= l(Date.parse(value), locale: @submission.template.account.locale, format: :long) %>
+ <%= TimeUtils.format_date_string(value, field.dig('preferences', 'format'), @submission.template.account.locale) %>
<% else %>
<%= Array.wrap(value).join(', ') %>
<% end %>
diff --git a/lib/submissions/generate_audit_trail.rb b/lib/submissions/generate_audit_trail.rb
index 2ac8a7e7d..ca54de588 100644
--- a/lib/submissions/generate_audit_trail.rb
+++ b/lib/submissions/generate_audit_trail.rb
@@ -228,7 +228,10 @@ def call(submission)
elsif field['type'] == 'checkbox'
composer.formatted_text_box([{ text: value.to_s.titleize }], padding: [0, 0, 10, 0])
else
- value = I18n.l(Date.parse(value), format: :long, locale: account.locale) if field['type'] == 'date'
+ if field['type'] == 'date'
+ value = TimeUtils.format_date_string(value, field.dig('preferences', 'format'), account.locale)
+ end
+
value = value.join(', ') if value.is_a?(Array)
composer.formatted_text_box([{ text: value.to_s.presence || 'n/a' }], padding: [0, 0, 10, 0])
diff --git a/lib/submissions/generate_result_attachments.rb b/lib/submissions/generate_result_attachments.rb
index 42eb8550e..62b9806aa 100644
--- a/lib/submissions/generate_result_attachments.rb
+++ b/lib/submissions/generate_result_attachments.rb
@@ -164,7 +164,9 @@ def call(submitter)
height - (area['y'] * height))
end
else
- value = I18n.l(Date.parse(value), format: :default, locale: account.locale) if field['type'] == 'date'
+ if field['type'] == 'date'
+ value = TimeUtils.format_date_string(value, field.dig('preferences', 'format'), account.locale)
+ end
text = HexaPDF::Layout::TextFragment.create(Array.wrap(value).join(', '), font: pdf.fonts.add(FONT_NAME),
font_size:)
@@ -268,7 +270,7 @@ def build_pdfs_index(submitter)
Submissions::EnsureResultGenerated.call(latest_submitter) if latest_submitter
documents = latest_submitter&.documents&.preload(:blob).to_a.presence
- documents ||= submitter.submission.template.schema_documents.preload(:blob)
+ documents ||= submitter.submission.template_schema_documents.preload(:blob)
documents.to_h do |attachment|
pdf =
diff --git a/lib/time_utils.rb b/lib/time_utils.rb
index c708ead5a..3c87ed5ff 100644
--- a/lib/time_utils.rb
+++ b/lib/time_utils.rb
@@ -1,6 +1,26 @@
# frozen_string_literal: true
module TimeUtils
+ MONTH_FORMATS = {
+ 'M' => '%-m',
+ 'MM' => '%m',
+ 'MMM' => '%b',
+ 'MMMM' => '%B'
+ }.freeze
+
+ DAY_FORMATS = {
+ 'D' => '%-d',
+ 'DD' => '%d'
+ }.freeze
+
+ YEAR_FORMATS = {
+ 'YYYY' => '%Y',
+ 'YY' => '%y'
+ }.freeze
+
+ DEFAULT_DATE_FORMAT_US = 'MM/DD/YYYY'
+ DEFAULT_DATE_FORMAT = 'DD/MM/YYYY'
+
module_function
def timezone_abbr(timezone, time = Time.current)
@@ -10,4 +30,20 @@ def timezone_abbr(timezone, time = Time.current)
tz_info.abbreviation(time)
end
+
+ def format_date_string(string, format, locale)
+ date = Date.parse(string)
+
+ format ||= locale.to_s.ends_with?('US') ? DEFAULT_DATE_FORMAT_US : DEFAULT_DATE_FORMAT
+
+ i18n_format = format.sub(/D+/, DAY_FORMATS[format[/D+/]])
+ .sub(/M+/, MONTH_FORMATS[format[/M+/]])
+ .sub(/Y+/, YEAR_FORMATS[format[/Y+/]])
+
+ I18n.l(date, format: i18n_format, locale:)
+ rescue Date::Error => e
+ Rollbar.error(e) if defined?(Rollbar)
+
+ string
+ end
end