# frozen_string_literal: true RSpec.describe DiscourseAi::AiHelper::DateFormatter do fab!(:user) # Reference time is Tuesday Jan 16th, 2024 at 2:30 PM Sydney time let(:sydney_reference) { DateTime.parse("2024-01-16 14:30:00 +11:00") } describe ".process_date_placeholders" do describe "with Sydney timezone" do before do user.user_option.update!(timezone: "Australia/Sydney") freeze_time(sydney_reference) end describe "date_time_offset_minutes" do it "handles minute offsets" do expect( described_class.process_date_placeholders( "Meeting in {{date_time_offset_minutes:30}}", user, ), ).to eq("Meeting in [date=2024-01-16 time=15:00:00 timezone=\"Australia/Sydney\"]") expect( described_class.process_date_placeholders( "Meeting in {{date_time_offset_minutes:90}}", user, ), ).to eq("Meeting in [date=2024-01-16 time=16:00:00 timezone=\"Australia/Sydney\"]") end it "handles minute ranges" do expect( described_class.process_date_placeholders( "Meeting {{date_time_offset_minutes:60:180}}", user, ), ).to eq( "Meeting [date-range from=2024-01-16T15:30:00 to=2024-01-16T17:30:00 timezone=\"Australia/Sydney\"]", ) end end describe "date_offset_days" do it "handles day offsets" do expect( described_class.process_date_placeholders("Due {{date_offset_days:1}}", user), ).to eq("Due [date=2024-01-17 timezone=\"Australia/Sydney\"]") expect( described_class.process_date_placeholders("Due {{date_offset_days:7}}", user), ).to eq("Due [date=2024-01-23 timezone=\"Australia/Sydney\"]") end it "handles day ranges" do expect( described_class.process_date_placeholders("Event {{date_offset_days:1:3}}", user), ).to eq("Event [date-range from=2024-01-17 to=2024-01-19 timezone=\"Australia/Sydney\"]") end end describe "datetime" do it "handles absolute times today" do expect( described_class.process_date_placeholders("Meeting at {{datetime:2pm}}", user), ).to eq("Meeting at [date=2024-01-16 time=14:00:00 timezone=\"Australia/Sydney\"]") expect( described_class.process_date_placeholders("Meeting at {{datetime:10pm}}", user), ).to eq("Meeting at [date=2024-01-16 time=22:00:00 timezone=\"Australia/Sydney\"]") end it "handles absolute times with day offset" do expect( described_class.process_date_placeholders("Meeting {{datetime:2pm+1}}", user), ).to eq("Meeting [date=2024-01-17 time=14:00:00 timezone=\"Australia/Sydney\"]") end it "handles time ranges" do expect( described_class.process_date_placeholders("Meeting {{datetime:2pm:4pm}}", user), ).to eq( "Meeting [date-range from=2024-01-16T14:00:00 to=2024-01-16T16:00:00 timezone=\"Australia/Sydney\"]", ) end it "handles time ranges with day offsets" do expect( described_class.process_date_placeholders("Meeting {{datetime:2pm+1:4pm+1}}", user), ).to eq( "Meeting [date-range from=2024-01-17T14:00:00 to=2024-01-17T16:00:00 timezone=\"Australia/Sydney\"]", ) end it "handles 12-hour time edge cases" do expect(described_class.process_date_placeholders("At {{datetime:12am}}", user)).to eq( "At [date=2024-01-16 time=00:00:00 timezone=\"Australia/Sydney\"]", ) expect(described_class.process_date_placeholders("At {{datetime:12pm}}", user)).to eq( "At [date=2024-01-16 time=12:00:00 timezone=\"Australia/Sydney\"]", ) end end describe "next_week" do it "handles next week days" do expect( described_class.process_date_placeholders("Meeting {{next_week:tuesday}}", user), ).to eq("Meeting [date=2024-01-23 timezone=\"Australia/Sydney\"]") end it "handles next week with specific times" do expect( described_class.process_date_placeholders("Meeting {{next_week:tuesday-2pm}}", user), ).to eq("Meeting [date=2024-01-23 time=14:00:00 timezone=\"Australia/Sydney\"]") end it "handles next week time ranges" do expect( described_class.process_date_placeholders( "Meeting {{next_week:tuesday-1pm:tuesday-3pm}}", user, ), ).to eq( "Meeting [date-range from=2024-01-23T13:00:00 to=2024-01-23T15:00:00 timezone=\"Australia/Sydney\"]", ) end end end describe "with Los Angeles timezone" do before do user.user_option.update!(timezone: "America/Los_Angeles") # Still freeze at Sydney time, but formatter should work in LA time freeze_time(sydney_reference) end it "handles current time conversions" do # When it's 2:30 PM Tuesday in Sydney # It's 7:30 PM Monday in LA expect( described_class.process_date_placeholders( "Meeting {{date_time_offset_minutes:30}}", user, ), ).to eq("Meeting [date=2024-01-15 time=20:00:00 timezone=\"America/Los_Angeles\"]") end it "handles absolute times" do expect(described_class.process_date_placeholders("Meeting {{datetime:2pm}}", user)).to eq( "Meeting [date=2024-01-15 time=14:00:00 timezone=\"America/Los_Angeles\"]", ) end describe "next_week" do it "handles next week days in LA time" do # From Monday night in LA (Tuesday in Sydney) expect( described_class.process_date_placeholders("Meeting {{next_week:tuesday}}", user), ).to eq("Meeting [date=2024-01-23 timezone=\"America/Los_Angeles\"]") end it "handles next week with specific times in LA" do expect( described_class.process_date_placeholders("Meeting {{next_week:tuesday-2pm}}", user), ).to eq("Meeting [date=2024-01-23 time=14:00:00 timezone=\"America/Los_Angeles\"]") end it "handles next week time ranges in LA" do expect( described_class.process_date_placeholders( "Meeting {{next_week:tuesday-1pm:tuesday-3pm}}", user, ), ).to eq( "Meeting [date-range from=2024-01-23T13:00:00 to=2024-01-23T15:00:00 timezone=\"America/Los_Angeles\"]", ) end end it "handles day transitions across timezones" do expect(described_class.process_date_placeholders("Due {{date_offset_days:1}}", user)).to eq( "Due [date=2024-01-16 timezone=\"America/Los_Angeles\"]", ) end end describe "with UTC timezone" do before do user.user_option.update!(timezone: nil) # defaults to UTC freeze_time(sydney_reference) end it "defaults to UTC for users without timezone" do # When it's 2:30 PM in Sydney # It's 3:30 AM in UTC expect( described_class.process_date_placeholders( "Meeting {{date_time_offset_minutes:30}}", user, ), ).to eq("Meeting [date=2024-01-16 time=04:00:00 timezone=\"UTC\"]") end describe "next_week" do it "handles next week calculations in UTC" do expect( described_class.process_date_placeholders("Meeting {{next_week:tuesday-2pm}}", user), ).to eq("Meeting [date=2024-01-23 time=14:00:00 timezone=\"UTC\"]") end end end describe "error handling" do before do user.user_option.update!(timezone: "Australia/Sydney") freeze_time(sydney_reference) end it "raises on invalid day name" do expect { described_class.process_date_placeholders("Meeting {{next_week:notaday}}", user) }.to raise_error(ArgumentError) end it "raises on invalid time format" do expect { described_class.process_date_placeholders("Meeting {{datetime:25pm}}", user) }.to raise_error(ArgumentError) end end describe "mixed formats" do before do user.user_option.update!(timezone: "Australia/Sydney") freeze_time(sydney_reference) end it "handles multiple different formats in the same text" do input = [ "Meeting {{datetime:2pm+1}},", "duration {{date_time_offset_minutes:60:180}},", "repeats until {{date_offset_days:7}}", "with sessions {{next_week:tuesday-1pm:tuesday-3pm}}", ].join(" ") expected = [ "Meeting [date=2024-01-17 time=14:00:00 timezone=\"Australia/Sydney\"],", "duration [date-range from=2024-01-16T15:30:00 to=2024-01-16T17:30:00 timezone=\"Australia/Sydney\"],", "repeats until [date=2024-01-23 timezone=\"Australia/Sydney\"]", "with sessions [date-range from=2024-01-23T13:00:00 to=2024-01-23T15:00:00 timezone=\"Australia/Sydney\"]", ].join(" ") expect(described_class.process_date_placeholders(input, user)).to eq(expected) end end end end