diff --git a/enterprise/app/services/captain/audience_matcher.rb b/enterprise/app/services/captain/audience_matcher.rb index 0fbc7c691..007960871 100644 --- a/enterprise/app/services/captain/audience_matcher.rb +++ b/enterprise/app/services/captain/audience_matcher.rb @@ -100,6 +100,8 @@ class Captain::AudienceMatcher def compare(actual, expected) return nil if actual.blank? + actual = Time.zone.parse(actual) if iso_date_string?(actual) + if actual.is_a?(Date) || actual.acts_like?(:time) actual.to_time <=> Time.zone.parse(expected.to_s) else @@ -109,6 +111,14 @@ class Captain::AudienceMatcher nil end + # Custom date attributes store ISO strings in jsonb; treat them as dates the way + # Contacts::FilterService does (it casts them in SQL). + def iso_date_string?(value) + value.is_a?(String) && Date.iso8601(value).present? + rescue ArgumentError + false + end + def older_than_days?(actual, days) date = to_date(actual) date.present? && date < Time.zone.today - days.to_i.days diff --git a/spec/enterprise/services/captain/audience_matcher_spec.rb b/spec/enterprise/services/captain/audience_matcher_spec.rb index 8f574f083..279e0439c 100644 --- a/spec/enterprise/services/captain/audience_matcher_spec.rb +++ b/spec/enterprise/services/captain/audience_matcher_spec.rb @@ -61,6 +61,13 @@ RSpec.describe Captain::AudienceMatcher do expect(matches?(leaf('created_at', 'days_before', '30'))).to be(true) expect(matches?(leaf('created_at', 'days_before', '60'))).to be(false) end + + it 'compares date custom attributes stored as ISO strings' do + contact.update!(custom_attributes: contact.custom_attributes.merge('signed_up_on' => '2024-01-15')) + expect(matches?(leaf('signed_up_on', 'is_greater_than', '2024-01-01'))).to be(true) + expect(matches?(leaf('signed_up_on', 'is_less_than', '2024-01-01'))).to be(false) + expect(matches?(leaf('signed_up_on', 'is_less_than', '2024-02-01'))).to be(true) + end end context 'with labels' do