Skip to content

Commit 7f107ec

Browse files
committed
fix(date): handle end of month correctly
- handle 30d month after 31d month correctly - handle February correctly including leap years
1 parent 04d5f68 commit 7f107ec

File tree

2 files changed

+73
-1
lines changed

2 files changed

+73
-1
lines changed

lua/orgmode/objects/date.lua

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,8 @@ function Date:end_of(span)
481481
end
482482

483483
if span == 'month' then
484-
return self:add({ month = 1 }):start_of('month'):adjust('-1d'):end_of('day')
484+
local date = os.date('*t', self.timestamp)
485+
return self:set({ day = Date._days_of_month(date) }):end_of('day')
485486
end
486487

487488
return self
@@ -517,6 +518,7 @@ end
517518
---@return OrgDate
518519
function Date:add(opts)
519520
opts = opts or {}
521+
---@type table
520522
local date = os.date('*t', self.timestamp)
521523
for opt, val in pairs(opts) do
522524
if opt == 'week' then
@@ -525,9 +527,31 @@ function Date:add(opts)
525527
end
526528
date[opt] = date[opt] + val
527529
end
530+
if opts['month'] then
531+
date['day'] = math.min(date['day'], Date._days_of_month(date))
532+
end
528533
return self:from_time_table(date)
529534
end
530535

536+
---@param date table
537+
---@return number
538+
function Date._days_of_month(date)
539+
local days_of = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
540+
if date.month == 2 then
541+
return Date._days_of_february(date.year)
542+
else
543+
return days_of[date.month]
544+
end
545+
end
546+
547+
function Date._days_of_february(year)
548+
return Date._is_leap_year(year) and 29 or 28
549+
end
550+
551+
function Date._is_leap_year(year)
552+
return year % 400 == 0 or (year % 100 ~= 0 and year % 4 == 0)
553+
end
554+
531555
---@param opts table
532556
---@return OrgDate
533557
function Date:subtract(opts)

tests/plenary/object/date_spec.lua

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -818,4 +818,52 @@ describe('Date object', function()
818818
local end_of_2021 = Date.from_string('2021-12-31')
819819
assert.are.same('52', end_of_2021:get_week_number())
820820
end)
821+
822+
it('should add month correctly | long month + short month', function()
823+
local date = Date.from_string('2021-05-31')
824+
assert.are.same('2021-05-31 Mon', date:to_string())
825+
assert.are.same('2021-06-30 Wed', date:add({ month = 1 }):to_string())
826+
end)
827+
828+
it('should add month correctly | short month + long month', function()
829+
local date = Date.from_string('2021-04-30')
830+
assert.are.same('2021-04-30 Fri', date:to_string())
831+
assert.are.same('2021-05-30 Sun', date:add({ month = 1 }):to_string())
832+
end)
833+
834+
it('should add month correctly | long month + february', function()
835+
local date = Date.from_string('2021-01-31')
836+
assert.are.same('2021-01-31 Sun', date:to_string())
837+
assert.are.same('2021-02-28 Sun', date:add({ month = 1 }):to_string())
838+
end)
839+
840+
it('should add month correctly | long month + february in leap year', function()
841+
local date = Date.from_string('2024-01-31')
842+
assert.are.same('2024-01-31 Wed', date:to_string())
843+
assert.are.same('2024-02-29 Thu', date:add({ month = 1 }):to_string())
844+
end)
845+
846+
it('should calculate end of month correctly | long month', function()
847+
local date = Date.from_string('2021-05-31')
848+
assert.are.same('2021-05-31 Mon', date:to_string())
849+
assert.are.same('2021-05-31 Mon', date:end_of('month'):to_string())
850+
end)
851+
852+
it('should calculate end of month correctly | short month', function()
853+
local date = Date.from_string('2021-04-30')
854+
assert.are.same('2021-04-30 Fri', date:to_string())
855+
assert.are.same('2021-04-30 Fri', date:end_of('month'):to_string())
856+
end)
857+
858+
it('should calculate end of month correctly | february', function()
859+
local date = Date.from_string('2021-02-28')
860+
assert.are.same('2021-02-28 Sun', date:to_string())
861+
assert.are.same('2021-02-28 Sun', date:end_of('month'):to_string())
862+
end)
863+
864+
it('should calculate end of month correctly | february leap-year', function()
865+
local date = Date.from_string('2024-02-29')
866+
assert.are.same('2024-02-29 Thu', date:to_string())
867+
assert.are.same('2024-02-29 Thu', date:end_of('month'):to_string())
868+
end)
821869
end)

0 commit comments

Comments
 (0)