From b0eb92651ae427a5a910f3a57b5f24babacc9a96 Mon Sep 17 00:00:00 2001 From: dhthwy <302825+dhthwy@users.noreply.github.com> Date: Fri, 3 Nov 2023 16:49:15 -0400 Subject: [PATCH 1/5] new datetime API --- library/lua/datetime.lua | 240 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 240 insertions(+) create mode 100644 library/lua/datetime.lua diff --git a/library/lua/datetime.lua b/library/lua/datetime.lua new file mode 100644 index 0000000000..d3b033745e --- /dev/null +++ b/library/lua/datetime.lua @@ -0,0 +1,240 @@ +local _ENV = mkmodule('datetime') + +--[[ +TODO: investigate applicability for adv mode. +if advmode then TICKS_PER_DAY = 86400 ? or only for cur_year_tick? +advmod_TU / 72 = ticks +--]] + +-- are these locals better in DwarfCalendar? +local DAYS_PER_MONTH = 28 +local DAYS_PER_YEAR = 336 +local MONTHS_PER_YEAR = 12 +local TICKS_PER_DAY = 1200 +local TICKS_PER_MONTH = TICKS_PER_DAY * DAYS_PER_MONTH +local TICKS_PER_YEAR = TICKS_PER_MONTH * MONTHS_PER_YEAR + +local MONTHS = { + 'Granite', + 'Slate', + 'Felsite', + 'Hematite', + 'Malachite', + 'Galena', + 'Limestone', + 'Sandstone', + 'Timber', + 'Moonstone', + 'Opal', + 'Obsidian', +} + +DwarfCalendar = defclass(DwarfCalendar) + +DwarfCalendar.ATTRS{ + year=0, + year_tick=0, +} + +function DwarfCalendar:init() + self:normalize() +end + +function DwarfCalendar:addTicks(ticks) + self.year_tick = self.year_tick + ticks + self:normalize() +end + +function DwarfCalendar:addDays(days) + self.year_tick = self.year_tick + self.daysToTicks(days) + self:normalize() +end + +function DwarfCalendar:addMonths(months) + self.year_tick = self.year_tick + self.monthsToTicks(months) + self:normalize() +end + +function DwarfCalendar:addYears(years) + self.year = self.year + years +end + +-- returns an integer pair: (year), (year tick count) +function DwarfCalendar:getYears() + return self.year, self.year_tick +end + +-- returns days since beginning of a year, starting from zero +function DwarfCalendar:ticksToDays() + return self.year_tick // TICKS_PER_DAY +end + +-- returns days since the beginning of a month, starting from zero +function DwarfCalendar:ticksToDayOfMonth() + return self:ticksToDays() % DAYS_PER_MONTH +end + +-- returns months since the beginning of a year, starting from zero +function DwarfCalendar:ticksToMonths() + return self.year_tick // TICKS_PER_MONTH +end + +-- returns ticks since the beginning of a day +function DwarfCalendar:getDayTicks() + return self.year_tick % TICKS_PER_DAY +end + +-- returns ticks since the beginning of a month +function DwarfCalendar:getMonthTicks() + return self.year_tick % TICKS_PER_MONTH +end + +function DwarfCalendar:normalize() + if (self.year_tick > TICKS_PER_YEAR) then + self.year = self.year + (self.year_tick // TICKS_PER_YEAR) + self.year_tick = self.year_tick % TICKS_PER_YEAR + elseif (self.year_tick < 0) then + -- going backwards in time, subtract year by at least one. + self.year = self.year - math.max(1, math.abs(self.year_tick) // TICKS_PER_YEAR) + -- Lua's modulo operator applies floor division, + -- hence year_tick will always be positive after assignment + -- equivalent to: year_tick - (TICKS_PER_YEAR * (year_tick // TICKS_PER_YEAR)) + self.year_tick = self.year_tick % TICKS_PER_YEAR + end +end + +function DwarfCalendar:__add(other) + if DEBUG then self:_debugOps(other) end + -- normalize() handles adjustments to year and year_tick + return DwarfCalendar{ year = (self.year + other.year), year_tick = (self.year_tick + other.year_tick) } +end + +function DwarfCalendar:__sub(other) + if DEBUG then self:_debugOps(other) end + -- normalize() handles adjustments to year and year_tick + return DwarfCalendar{ year = (self.year - other.year) , year_tick = (self.year_tick - other.year_tick) } +end + +function DwarfCalendar:_debugOps(other) + print('first: '..self.year,self.year_tick) + print('second: '..other.year,other.year_tick) +end + +-- utility functions +function DwarfCalendar.daysToTicks(days) + return days * TICKS_PER_DAY +end + +function DwarfCalendar.monthsToTicks(months) + return months * TICKS_PER_MONTH +end + +function DwarfCalendar.getMonthNames() + return MONTHS +end + + +DateTime = defclass(DateTime, DwarfCalendar) + +-- returns an integer pair: (day of month starting from 1), (day tick count) +function DateTime:getDayOfMonth() + return self:ticksToDayOfMonth() + 1, self:getDayTicks() +end + +-- returns a string in ordinal form (e.g. 1st, 12th, 22nd, 101st, 111th, 133rd) +function DateTime:getDayOfMonthWithSuffix() + local d = self:getDayOfMonth() + return tostring(d)..self.getOrdinalSuffix(d) +end + +-- returns an integer pair: (current day of year, from 1), (day tick count) +function DateTime:getDayOfYear() + return self:ticksToDays() + 1, self:getDayTicks() +end + +-- returns an integer pair: (current month of the year, from 1), (month tick count) +function DateTime:getMonth() + return self:ticksToMonths() + 1, self:getMonthTicks() +end + +-- returns a string of the current month of the year +function DateTime:getNameOfMonth() + return MONTHS[self:getMonth()] or error("getMonth(): bad index?") +end + +function DateTime:toDuration() + return Duration{ year = self.year, year_tick = self.year_tick } +end + +function DateTime:__add(other) + if DEBUG then self:_debugOps(other) end + -- normalize() handles adjustments to year and year_tick + return DateTime{ year = (self.year + other.year), year_tick = (self.year_tick + other.year_tick) } +end + +-- might make sense to return a Duration here +function DateTime:__sub(other) + if DEBUG then self:_debugOps(other) end + -- normalize() handles adjustments to year and year_tick + return DateTime{ year = (self.year - other.year) , year_tick = (self.year_tick - other.year_tick) } +end + +-- Static functions +function DateTime.now() + return DateTime{ year = df.global.cur_year, year_tick = df.global.cur_year_tick } +end + +-- Ordinal suffix rules found here: https://en.wikipedia.org/wiki/Ordinal_indicator +function DateTime.getOrdinalSuffix(ordinal) + if (ordinal < 0) then + ordinal = math.abs(ordinal) + end + + local rem = (ordinal < 100) and (ordinal % 10) or (ordinal % 100) + -- rem can be between 11 and 13 only when ordinal is > 100 + if (ordinal >= 11 and ordinal <= 13) or (rem >= 11 and rem <= 13) then + return 'th' + end + -- modulo again to handle the case when ordinal is > 100 + return ({'st', 'nd', 'rd'})[rem % 10] or 'th' +end + +Duration = defclass(Duration, DwarfCalendar) + +-- returns ticks since year zero +function Duration:getTicks() + return self.year * TICKS_PER_YEAR + self.year_tick +end + +-- returns an integer pair: (days since year zero), (day tick count) +function Duration:getDays() + return self.year * DAYS_PER_YEAR + self:ticksToDays(), self:getDayTicks() +end + +-- returns an integer pair: (months since year zero), (month tick count) +function Duration:getMonths() + return self.year * MONTHS_PER_YEAR + self:ticksToMonths(), self:getMonthTicks() +end + +-- returns parts of an elapsed time: +-- years, +-- months, - since start of year +-- days, - since start of month +-- day tick count - since start of day +function Duration:getYearsMonthsDays() + return self.year, self:ticksToMonths(), self:ticksToDayOfMonth(), self:getDayTicks() +end + +function Duration:__add(other) + if DEBUG then self:_debugOps(other) end + -- normalize() handles adjustments to year and year_tick + return Duration{ year = (self.year + other.year), year_tick = (self.year_tick + other.year_tick) } +end + +function Duration:__sub(other) + if DEBUG then self:_debugOps(other) end + -- normalize() handles adjustments to year and year_tick + return Duration{ year = (self.year - other.year) , year_tick = (self.year_tick - other.year_tick) } +end + +return _ENV From ae9d49fad8385f272f816cd3460e95b9c6647e4f Mon Sep 17 00:00:00 2001 From: dhthwy <302825+dhthwy@users.noreply.github.com> Date: Mon, 6 Nov 2023 21:33:05 -0500 Subject: [PATCH 2/5] datetime: support adv mode, fullmoon --- library/lua/datetime.lua | 296 +++++++++++++++++++++++++++------------ 1 file changed, 207 insertions(+), 89 deletions(-) diff --git a/library/lua/datetime.lua b/library/lua/datetime.lua index d3b033745e..1735120d07 100644 --- a/library/lua/datetime.lua +++ b/library/lua/datetime.lua @@ -1,72 +1,161 @@ local _ENV = mkmodule('datetime') ---[[ -TODO: investigate applicability for adv mode. -if advmode then TICKS_PER_DAY = 86400 ? or only for cur_year_tick? -advmod_TU / 72 = ticks ---]] - --- are these locals better in DwarfCalendar? -local DAYS_PER_MONTH = 28 -local DAYS_PER_YEAR = 336 -local MONTHS_PER_YEAR = 12 -local TICKS_PER_DAY = 1200 -local TICKS_PER_MONTH = TICKS_PER_DAY * DAYS_PER_MONTH -local TICKS_PER_YEAR = TICKS_PER_MONTH * MONTHS_PER_YEAR - -local MONTHS = { - 'Granite', - 'Slate', - 'Felsite', - 'Hematite', - 'Malachite', - 'Galena', - 'Limestone', - 'Sandstone', - 'Timber', - 'Moonstone', - 'Opal', - 'Obsidian', +local DAYS_PER_MONTH = 28 +local DAYS_PER_YEAR = 336 +local MONTHS_PER_YEAR = 12 + +DWARF_TICKS_PER_DAY = 1200 +--local DWARF_TICKS_PER_MONTH = DWARF_TICKS_PER_DAY * DAYS_PER_MONTH +--local DWARF_TICKS_PER_YEAR = DWARF_TICKS_PER_MONTH * MONTHS_PER_YEAR + +ADVENTURE_TICKS_PER_DAY = 172800 +--local ADVENTURE_TICKS_PER_MONTH = ADVENTURE_TICKS_PER_DAY * DAYS_PER_MONTH +--local ADVENTURE_TICKS_PER_YEAR = ADVENTURE_TICKS_PER_MONTH * MONTHS_PER_YEAR + +--local TICKS_PER_DAY = DWARF_TICKS_PER_DAY +--local TICKS_PER_MONTH = DWARF_TICKS_PER_MONTH +--local TICKS_PER_YEAR = TICKS_PER_MONTH * MONTHS_PER_YEAR + +local CALENDAR_MONTHS = { + 'Granite', + 'Slate', + 'Felsite', + 'Hematite', + 'Malachite', + 'Galena', + 'Limestone', + 'Sandstone', + 'Timber', + 'Moonstone', + 'Opal', + 'Obsidian' } +-- day -> month +local CALENDAR_FULLMOON_MAP = { + [25] = 1, + [23] = 2, + [21] = 3, + [19] = 4, + [17] = 5, + [15] = 6, + [13] = 7, + [10] = 8, + [8] = 9, + [6] = 10, + [4] = 11, + [2] = 12, + [28] = 12 +} + +-- Ordinal suffix rules found here: https://en.wikipedia.org/wiki/Ordinal_indicator +-- global for scripts to use +function getOrdinalSuffix(ordinal) + if (ordinal < 0) then + ordinal = math.abs(ordinal) + end + + local rem = (ordinal < 100) and (ordinal % 10) or (ordinal % 100) + -- rem can be between 11 and 13 only when ordinal is > 100 + if (ordinal >= 11 and ordinal <= 13) or (rem >= 11 and rem <= 13) then + return 'th' + end + -- modulo again to handle the case when ordinal is > 100 + return ({'st', 'nd', 'rd'})[rem % 10] or 'th' +end + DwarfCalendar = defclass(DwarfCalendar) DwarfCalendar.ATTRS{ year=0, year_tick=0, + ticks_per_day=DWARF_TICKS_PER_DAY } function DwarfCalendar:init() + self:setTickRates(self.ticks_per_day) self:normalize() end +function DwarfCalendar:setTickRates(ticks_per_day) + -- game_mode.DWARF and .ADVENTURE values are < 10 + -- too low for sane tick rates, so we can utilize em. + -- this might be useful if the caller wants to set by game mode + if (ticks_per_day == df.game_mode.DWARF) then + self.ticks_per_day = DWARF_TICKS_PER_DAY + elseif (ticks_per_day == df.game_mode.ADVENTURE) + self.ticks_per_day = ADVENTURE_TICKS_PER_DAY + else + -- should we throw an error if caller passed in <= 0 here? + -- if not, we'll divide by zero later + self.ticks_per_day = (ticks_per_day > 0) and ticks_per_day or DWARF_TICKS_PER_DAY + end + self.ticks_per_month = self.ticks_per_day * DAYS_PER_MONTH + self.ticks_per_year = self.ticks_per_month * MONTHS_PER_YEAR +end + function DwarfCalendar:addTicks(ticks) self.year_tick = self.year_tick + ticks self:normalize() + return self end function DwarfCalendar:addDays(days) self.year_tick = self.year_tick + self.daysToTicks(days) self:normalize() + return self end function DwarfCalendar:addMonths(months) self.year_tick = self.year_tick + self.monthsToTicks(months) self:normalize() + return self end function DwarfCalendar:addYears(years) self.year = self.year + years + return self end +-- should this be named getYear()? -- returns an integer pair: (year), (year tick count) function DwarfCalendar:getYears() return self.year, self.year_tick end +function DwarfCalendar:setDayOfMonth(month, day) + self.year_tick = DwarfCalendar.monthsToTicks(month) + DwarfCalendar.daysToTicks(day) +end + +-- returns an integer pair: (day of month starting from 1), (day tick count) +function DwarfCalendar:getDayOfMonth() + return self:ticksToDayOfMonth() + 1, self:getDayTicks() +end + +-- returns a string in ordinal form (e.g. 1st, 12th, 22nd, 101st, 111th, 133rd) +function DwarfCalendar:getDayOfMonthWithSuffix() + local d = self:getDayOfMonth() + return tostring(d)..getOrdinalSuffix(d) +end + +-- returns an integer pair: (current day of year, from 1), (day tick count) +function DwarfCalendar:getDayOfYear() + return self:ticksToDays() + 1, self:getDayTicks() +end + +-- returns an integer pair: (current month of the year, from 1), (month tick count) +function DwarfCalendar:getMonth() + return self:ticksToMonths() + 1, self:getMonthTicks() +end + +-- returns a string of the current month of the year +function DwarfCalendar:getNameOfMonth() + return CALENDAR_MONTHS[self:getMonth()] or error("bad index?") +end + -- returns days since beginning of a year, starting from zero function DwarfCalendar:ticksToDays() - return self.year_tick // TICKS_PER_DAY + return self.year_tick // self.ticks_per_day end -- returns days since the beginning of a month, starting from zero @@ -76,30 +165,76 @@ end -- returns months since the beginning of a year, starting from zero function DwarfCalendar:ticksToMonths() - return self.year_tick // TICKS_PER_MONTH + return self.year_tick // self.ticks_per_month end -- returns ticks since the beginning of a day function DwarfCalendar:getDayTicks() - return self.year_tick % TICKS_PER_DAY + return self.year_tick % self.ticks_per_day end -- returns ticks since the beginning of a month function DwarfCalendar:getMonthTicks() - return self.year_tick % TICKS_PER_MONTH + return self.year_tick % self.ticks_per_month +end + +function DwarfCalendar:daysToTicks(days) + return days * self.ticks_per_day +end + +function DwarfCalendar:monthsToTicks(months) + return months * self.ticks_per_month +end + +function DwarfCalendar:isFullMoon() + return (self:getMonth() == CALENDAR_FULLMOON_MAP[self:getDayOfMonth()]) + and true or false +end + +function DwarfCalendar:nextFullMoon() + local dateT = DateTime{ year = self.year, year_tick = self.year_tick } + if (dateT:isFullMoon()) then dateT:addDays(1) end + + local cur_m = dateT:getMonth() + local fm = { + 25, + 23, + 21, + 19, + 17, + 15, + 13, + 10, + 8, + 6, + 4, + 2, 28 + } + + if (dateT:getDayOfMonth() < fm[cur_m]) + dateT:setDayOfMonth(cur_m, fm[cur_m]) + else + -- Next full moon is on the next month + -- or next year if addDays() rolled us over. + -- Obsidian is a possible exception since it has 2 full moons + -- this also handles the case when Obsidian day is between 2 and 28 exclusive + dateT:setDayOfMonth(cur_m, fm[cur_m+1]) + end + + return dateT end function DwarfCalendar:normalize() - if (self.year_tick > TICKS_PER_YEAR) then - self.year = self.year + (self.year_tick // TICKS_PER_YEAR) - self.year_tick = self.year_tick % TICKS_PER_YEAR + if (self.year_tick > self.ticks_per_year) then + self.year = self.year + (self.year_tick // self.ticks_per_year) + self.year_tick = self.year_tick % self.ticks_per_year elseif (self.year_tick < 0) then -- going backwards in time, subtract year by at least one. - self.year = self.year - math.max(1, math.abs(self.year_tick) // TICKS_PER_YEAR) + self.year = self.year - math.max(1, math.abs(self.year_tick) // self.ticks_per_year) -- Lua's modulo operator applies floor division, -- hence year_tick will always be positive after assignment -- equivalent to: year_tick - (TICKS_PER_YEAR * (year_tick // TICKS_PER_YEAR)) - self.year_tick = self.year_tick % TICKS_PER_YEAR + self.year_tick = self.year_tick % self.ticks_per_year end end @@ -120,48 +255,42 @@ function DwarfCalendar:_debugOps(other) print('second: '..other.year,other.year_tick) end --- utility functions -function DwarfCalendar.daysToTicks(days) - return days * TICKS_PER_DAY -end - -function DwarfCalendar.monthsToTicks(months) - return months * TICKS_PER_MONTH -end function DwarfCalendar.getMonthNames() - return MONTHS + return CALENDAR_MONTHS end DateTime = defclass(DateTime, DwarfCalendar) --- returns an integer pair: (day of month starting from 1), (day tick count) -function DateTime:getDayOfMonth() - return self:ticksToDayOfMonth() + 1, self:getDayTicks() -end - --- returns a string in ordinal form (e.g. 1st, 12th, 22nd, 101st, 111th, 133rd) -function DateTime:getDayOfMonthWithSuffix() - local d = self:getDayOfMonth() - return tostring(d)..self.getOrdinalSuffix(d) -end - --- returns an integer pair: (current day of year, from 1), (day tick count) -function DateTime:getDayOfYear() - return self:ticksToDays() + 1, self:getDayTicks() -end - --- returns an integer pair: (current month of the year, from 1), (month tick count) -function DateTime:getMonth() - return self:ticksToMonths() + 1, self:getMonthTicks() -end - --- returns a string of the current month of the year -function DateTime:getNameOfMonth() - return MONTHS[self:getMonth()] or error("getMonth(): bad index?") -end +-- returns hours (24 hour format), minutes, seconds +function DateTime:getTime() + -- probably only useful in adv mode where a day is 144x longer + local h = self:getDayTicks() / (self.ticks_per_day / 24) + local m = (h * 60) % 60 + local s = (m * 60) % 60 + -- return as integers, rounded down + return h//1, m//1, s//1 +end + +-- TODO: maybe add setTime() + +--function DateTime:daysTo(other) +-- local dateT = other - self +-- return dateT.year * DAYS_PER_YEAR + dateT:ticksToDays() +--end +-- maybe useful addition +--function DateTime:daysTo(other) +-- return (other - self):toDuration().getDays() +--end + +-- Alternatively, instead of a Duration object, +-- we can simply synthesize a table with +-- key value pairs of years, months, days, ticks. +-- If table, it could optionally take an argument or variadic +-- where the caller provides the time unit specifiers +-- i.e. getDuration/toDuration('ymd') or toDuration('y', 'm', 'd'), etc function DateTime:toDuration() return Duration{ year = self.year, year_tick = self.year_tick } end @@ -179,31 +308,19 @@ function DateTime:__sub(other) return DateTime{ year = (self.year - other.year) , year_tick = (self.year_tick - other.year_tick) } end --- Static functions -function DateTime.now() - return DateTime{ year = df.global.cur_year, year_tick = df.global.cur_year_tick } -end - --- Ordinal suffix rules found here: https://en.wikipedia.org/wiki/Ordinal_indicator -function DateTime.getOrdinalSuffix(ordinal) - if (ordinal < 0) then - ordinal = math.abs(ordinal) - end - - local rem = (ordinal < 100) and (ordinal % 10) or (ordinal % 100) - -- rem can be between 11 and 13 only when ordinal is > 100 - if (ordinal >= 11 and ordinal <= 13) or (rem >= 11 and rem <= 13) then - return 'th' - end - -- modulo again to handle the case when ordinal is > 100 - return ({'st', 'nd', 'rd'})[rem % 10] or 'th' +function DateTime.now(game_mode) + game_mode = game_mode or df.global.gamemode + -- if game_mode is not given or not ADVENTURE then default to DWARF mode + local ticks = (game_mode == df.game_mode.ADVENTURE) and + (df.global.cur_year_tick_advmode) or (df.global.cur_year_tick) + return DateTime{ year = df.global.cur_year, year_tick = ticks } end Duration = defclass(Duration, DwarfCalendar) -- returns ticks since year zero function Duration:getTicks() - return self.year * TICKS_PER_YEAR + self.year_tick + return self.year * self.ticks_per_year + self.year_tick end -- returns an integer pair: (days since year zero), (day tick count) @@ -237,4 +354,5 @@ function Duration:__sub(other) return Duration{ year = (self.year - other.year) , year_tick = (self.year_tick - other.year_tick) } end + return _ENV From 90ab8e5b8cf4b19863d598f5b62328449dd43836 Mon Sep 17 00:00:00 2001 From: dhthwy <302825+dhthwy@users.noreply.github.com> Date: Tue, 7 Nov 2023 22:55:07 -0500 Subject: [PATCH 3/5] datetime: tests are in --- library/lua/datetime.lua | 63 +++++---- test/library/datetime.lua | 272 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 302 insertions(+), 33 deletions(-) create mode 100644 test/library/datetime.lua diff --git a/library/lua/datetime.lua b/library/lua/datetime.lua index 1735120d07..65855de03f 100644 --- a/library/lua/datetime.lua +++ b/library/lua/datetime.lua @@ -1,8 +1,8 @@ local _ENV = mkmodule('datetime') -local DAYS_PER_MONTH = 28 -local DAYS_PER_YEAR = 336 -local MONTHS_PER_YEAR = 12 +DAYS_PER_MONTH = 28 +DAYS_PER_YEAR = 336 +MONTHS_PER_YEAR = 12 DWARF_TICKS_PER_DAY = 1200 --local DWARF_TICKS_PER_MONTH = DWARF_TICKS_PER_DAY * DAYS_PER_MONTH @@ -16,7 +16,7 @@ ADVENTURE_TICKS_PER_DAY = 172800 --local TICKS_PER_MONTH = DWARF_TICKS_PER_MONTH --local TICKS_PER_YEAR = TICKS_PER_MONTH * MONTHS_PER_YEAR -local CALENDAR_MONTHS = { +CALENDAR_MONTHS = { 'Granite', 'Slate', 'Felsite', @@ -73,25 +73,24 @@ DwarfCalendar.ATTRS{ } function DwarfCalendar:init() - self:setTickRates(self.ticks_per_day) + self:setTickRate(self.ticks_per_day) self:normalize() end -function DwarfCalendar:setTickRates(ticks_per_day) +function DwarfCalendar:setTickRate(ticks_per_day) -- game_mode.DWARF and .ADVENTURE values are < 10 -- too low for sane tick rates, so we can utilize em. -- this might be useful if the caller wants to set by game mode if (ticks_per_day == df.game_mode.DWARF) then self.ticks_per_day = DWARF_TICKS_PER_DAY - elseif (ticks_per_day == df.game_mode.ADVENTURE) + elseif (ticks_per_day == df.game_mode.ADVENTURE) then self.ticks_per_day = ADVENTURE_TICKS_PER_DAY else - -- should we throw an error if caller passed in <= 0 here? - -- if not, we'll divide by zero later self.ticks_per_day = (ticks_per_day > 0) and ticks_per_day or DWARF_TICKS_PER_DAY end self.ticks_per_month = self.ticks_per_day * DAYS_PER_MONTH - self.ticks_per_year = self.ticks_per_month * MONTHS_PER_YEAR + self.ticks_per_year = self.ticks_per_day * DAYS_PER_YEAR + return self end function DwarfCalendar:addTicks(ticks) @@ -101,13 +100,13 @@ function DwarfCalendar:addTicks(ticks) end function DwarfCalendar:addDays(days) - self.year_tick = self.year_tick + self.daysToTicks(days) + self.year_tick = self.year_tick + self:daysToTicks(days) self:normalize() return self end function DwarfCalendar:addMonths(months) - self.year_tick = self.year_tick + self.monthsToTicks(months) + self.year_tick = self.year_tick + self:monthsToTicks(months) self:normalize() return self end @@ -123,8 +122,11 @@ function DwarfCalendar:getYears() return self.year, self.year_tick end -function DwarfCalendar:setDayOfMonth(month, day) - self.year_tick = DwarfCalendar.monthsToTicks(month) + DwarfCalendar.daysToTicks(day) +function DwarfCalendar:setDayOfMonth(day, month) + month = month or self:getMonth() + -- zero based when converting to ticks + self.year_tick = self:monthsToTicks(month-1) + self:daysToTicks(day-1) + return self end -- returns an integer pair: (day of month starting from 1), (day tick count) @@ -211,14 +213,14 @@ function DwarfCalendar:nextFullMoon() 2, 28 } - if (dateT:getDayOfMonth() < fm[cur_m]) - dateT:setDayOfMonth(cur_m, fm[cur_m]) + if (dateT:getDayOfMonth() < fm[cur_m]) then + dateT:setDayOfMonth(fm[cur_m], cur_m) else -- Next full moon is on the next month -- or next year if addDays() rolled us over. - -- Obsidian is a possible exception since it has 2 full moons - -- this also handles the case when Obsidian day is between 2 and 28 exclusive - dateT:setDayOfMonth(cur_m, fm[cur_m+1]) + -- Obsidian is special since it has 2 full moons + -- also handles the case when Obsidian day is between 2 and 28 exclusive + dateT:setDayOfMonth(fm[cur_m+1], cur_m) end return dateT @@ -241,13 +243,13 @@ end function DwarfCalendar:__add(other) if DEBUG then self:_debugOps(other) end -- normalize() handles adjustments to year and year_tick - return DwarfCalendar{ year = (self.year + other.year), year_tick = (self.year_tick + other.year_tick) } + return DwarfCalendar{ year = (self.year + other.year), year_tick = (self.year_tick + other.year_tick), ticks_per_day = self.ticks_per_day } end function DwarfCalendar:__sub(other) if DEBUG then self:_debugOps(other) end -- normalize() handles adjustments to year and year_tick - return DwarfCalendar{ year = (self.year - other.year) , year_tick = (self.year_tick - other.year_tick) } + return DwarfCalendar{ year = (self.year - other.year) , year_tick = (self.year_tick - other.year_tick), ticks_per_day = self.ticks_per_day } end function DwarfCalendar:_debugOps(other) @@ -255,15 +257,8 @@ function DwarfCalendar:_debugOps(other) print('second: '..other.year,other.year_tick) end - -function DwarfCalendar.getMonthNames() - return CALENDAR_MONTHS -end - - DateTime = defclass(DateTime, DwarfCalendar) - -- returns hours (24 hour format), minutes, seconds function DateTime:getTime() -- probably only useful in adv mode where a day is 144x longer @@ -292,20 +287,20 @@ end -- where the caller provides the time unit specifiers -- i.e. getDuration/toDuration('ymd') or toDuration('y', 'm', 'd'), etc function DateTime:toDuration() - return Duration{ year = self.year, year_tick = self.year_tick } + return Duration{ year = self.year, year_tick = self.year_tick, ticks_per_day = self.ticks_per_day } end function DateTime:__add(other) if DEBUG then self:_debugOps(other) end -- normalize() handles adjustments to year and year_tick - return DateTime{ year = (self.year + other.year), year_tick = (self.year_tick + other.year_tick) } + return DateTime{ year = (self.year + other.year), year_tick = (self.year_tick + other.year_tick), ticks_per_day = self.ticks_per_day } end -- might make sense to return a Duration here function DateTime:__sub(other) if DEBUG then self:_debugOps(other) end -- normalize() handles adjustments to year and year_tick - return DateTime{ year = (self.year - other.year) , year_tick = (self.year_tick - other.year_tick) } + return DateTime{ year = (self.year - other.year) , year_tick = (self.year_tick - other.year_tick), ticks_per_day = self.ticks_per_day } end function DateTime.now(game_mode) @@ -313,7 +308,10 @@ function DateTime.now(game_mode) -- if game_mode is not given or not ADVENTURE then default to DWARF mode local ticks = (game_mode == df.game_mode.ADVENTURE) and (df.global.cur_year_tick_advmode) or (df.global.cur_year_tick) - return DateTime{ year = df.global.cur_year, year_tick = ticks } + -- Tick rate defaults to DWARF mode, we should set the tick rate here as well + -- For a custom rate the caller can use setTickRate() or we can add a second + -- optional parameter + return DateTime{ year = df.global.cur_year, year_tick = ticks }:setTickRate(game_mode) end Duration = defclass(Duration, DwarfCalendar) @@ -354,5 +352,4 @@ function Duration:__sub(other) return Duration{ year = (self.year - other.year) , year_tick = (self.year_tick - other.year_tick) } end - return _ENV diff --git a/test/library/datetime.lua b/test/library/datetime.lua new file mode 100644 index 0000000000..3ee9ea0fe7 --- /dev/null +++ b/test/library/datetime.lua @@ -0,0 +1,272 @@ +config.target = 'core' + +local dateT = require('datetime') + +local DWARF_TICKS_PER_DAY = dateT.DWARF_TICKS_PER_DAY + +function test.getOrdinalSuffix() + expect.eq(dateT.getOrdinalSuffix(1), 'st') + expect.eq(dateT.getOrdinalSuffix(2), 'nd') + expect.eq(dateT.getOrdinalSuffix(3), 'rd') + expect.eq(dateT.getOrdinalSuffix(4), 'th') + expect.eq(dateT.getOrdinalSuffix(11), 'th') + expect.eq(dateT.getOrdinalSuffix(12), 'th') + expect.eq(dateT.getOrdinalSuffix(13), 'th') + expect.eq(dateT.getOrdinalSuffix(23), 'rd') + expect.eq(dateT.getOrdinalSuffix(111), 'th') + expect.eq(dateT.getOrdinalSuffix(112), 'th') + expect.eq(dateT.getOrdinalSuffix(113), 'th') + expect.eq(dateT.getOrdinalSuffix(123), 'rd') +end + +function test.normalize() + local year_ticks = DWARF_TICKS_PER_DAY * dateT.DAYS_PER_YEAR + local dt = dateT.DateTime {year = 1, year_tick = year_ticks+1} + expect.eq(dt.year, 2) + expect.eq(dt.year_tick, 1) + dt = dateT.DateTime {year = 2, year_tick = -1} + expect.eq(dt.year, 1) + expect.eq(dt.year_tick, year_ticks-1) +end + +function test.setTickRate() + local dt = dateT.DateTime {} + + dt:setTickRate(0) + expect.eq(dt.ticks_per_day, DWARF_TICKS_PER_DAY) + expect.eq(dt.ticks_per_month, DWARF_TICKS_PER_DAY * dateT.DAYS_PER_MONTH) + expect.eq(dt.ticks_per_year, DWARF_TICKS_PER_DAY * dateT.DAYS_PER_YEAR) + + dt:setTickRate(df.game_mode.DWARF) + expect.eq(dt.ticks_per_day, DWARF_TICKS_PER_DAY) + expect.eq(dt.ticks_per_month, DWARF_TICKS_PER_DAY * dateT.DAYS_PER_MONTH) + expect.eq(dt.ticks_per_year, DWARF_TICKS_PER_DAY * dateT.DAYS_PER_YEAR) + + dt:setTickRate(df.game_mode.ADVENTURE) + expect.eq(dt.ticks_per_day, dateT.ADVENTURE_TICKS_PER_DAY) + expect.eq(dt.ticks_per_month, dateT.ADVENTURE_TICKS_PER_DAY * dateT.DAYS_PER_MONTH) + expect.eq(dt.ticks_per_year, dateT.ADVENTURE_TICKS_PER_DAY * dateT.DAYS_PER_YEAR) + + dt:setTickRate(1000) + expect.eq(dt.ticks_per_day, 1000) + expect.eq(dt.ticks_per_month, 1000 * dateT.DAYS_PER_MONTH) + expect.eq(dt.ticks_per_year, 1000 * dateT.DAYS_PER_YEAR) +end + +function test.addTicks() + local dt = dateT.DateTime {year_tick = 0} + dt:addTicks(1) + expect.eq(dt.year_tick, 1) + dt:addTicks(-1) + expect.eq(dt.year_tick, 0) +end + +function test.addDays() + local dt = dateT.DateTime {year_tick = 0} + dt:addDays(1) + expect.eq(dt.year_tick, dt.ticks_per_day) + dt:addDays(-1) + expect.eq(dt.year_tick, 0) +end + +function test.addMonths() + local dt = dateT.DateTime {year_tick = 0} + dt:addMonths(1) + expect.eq(dt.year_tick, dt.ticks_per_month) + dt:addMonths(-1) + expect.eq(dt.year_tick, 0) +end + +function test.addYears() + local dt = dateT.DateTime {year = 0} + dt:addYears(1) + expect.eq(dt.year, 1) + dt:addYears(-1) + expect.eq(dt.year, 0) +end + +function test.getYears() + local dt = dateT.DateTime {year = 1, year_tick = 1} + local y, t = dt:getYears() + expect.eq(y, 1) + expect.eq(t, 1) +end + +function test.setDayOfMonth() + local dt = dateT.DateTime {year_tick = 0} + dt:setDayOfMonth(2, 1) + expect.eq(dt.year_tick, dt.ticks_per_day) +end + +function test.getDayOfMonth() + local dt = dateT.DateTime {year_tick = DWARF_TICKS_PER_DAY * dateT.DAYS_PER_MONTH + 1, ticks_per_day = DWARF_TICKS_PER_DAY} + local day, ticks = dt:getDayOfMonth() + expect.eq(day, 1) + expect.eq(ticks, 1) +end + +function test.getDayOfMonthWithSuffix() + local dt = dateT.DateTime {year_tick = DWARF_TICKS_PER_DAY * dateT.DAYS_PER_MONTH + 1, ticks_per_day = DWARF_TICKS_PER_DAY} + local day = dt:getDayOfMonthWithSuffix() + expect.eq(day, '1st') +end + +function test.getDayOfYear() + local dt = dateT.DateTime {year_tick = DWARF_TICKS_PER_DAY * dateT.DAYS_PER_MONTH + 1, ticks_per_day = DWARF_TICKS_PER_DAY} + local day, ticks = dt:getDayOfYear() + expect.eq(day, dateT.DAYS_PER_MONTH+1) + expect.eq(ticks, 1) +end + +function test.getMonth() + local dt = dateT.DateTime {year_tick = DWARF_TICKS_PER_DAY * dateT.DAYS_PER_MONTH + 1, ticks_per_day = DWARF_TICKS_PER_DAY} + local month, ticks = dt:getMonth() + expect.eq(month, 2) + expect.eq(ticks, 1) +end + +function test.getNameOfMonth() + local dt = dateT.DateTime {year_tick = DWARF_TICKS_PER_DAY * dateT.DAYS_PER_MONTH + 1, ticks_per_day = DWARF_TICKS_PER_DAY} + local name = dt:getNameOfMonth() + expect.eq(name, 'Slate') +end + +function test.ticksToDays() + local dt = dateT.DateTime {year_tick = DWARF_TICKS_PER_DAY * dateT.DAYS_PER_MONTH + 1, ticks_per_day = DWARF_TICKS_PER_DAY} + local days = dt:ticksToDays() + expect.eq(days, dateT.DAYS_PER_MONTH) +end + +function test.ticksToDayOfMonth() + local dt = dateT.DateTime {year_tick = DWARF_TICKS_PER_DAY * dateT.DAYS_PER_MONTH + 1, ticks_per_day = DWARF_TICKS_PER_DAY} + local days = dt:ticksToDayOfMonth() + expect.eq(days, 0) +end + +function test.ticksToMonths() + local dt = dateT.DateTime {year_tick = DWARF_TICKS_PER_DAY * dateT.DAYS_PER_MONTH + 1, ticks_per_day = DWARF_TICKS_PER_DAY} + local m = dt:ticksToMonths() + expect.eq(m, 1) +end + +function test.getDayTicks() + local dt = dateT.DateTime {year_tick = DWARF_TICKS_PER_DAY * dateT.DAYS_PER_MONTH + 1, ticks_per_day = DWARF_TICKS_PER_DAY} + local t = dt:getDayTicks() + expect.eq(t, 1) +end + +function test.getMonthTicks() + local dt = dateT.DateTime {year_tick = DWARF_TICKS_PER_DAY * dateT.DAYS_PER_MONTH + 1, ticks_per_day = DWARF_TICKS_PER_DAY} + local t = dt:getMonthTicks() + expect.eq(t, 1) +end + +function test.daysToTicks() + local dt = dateT.DateTime {ticks_per_day = DWARF_TICKS_PER_DAY} + local t = dt:daysToTicks(1) + expect.eq(t, DWARF_TICKS_PER_DAY) +end + +function test.monthsToTicks() + local dt = dateT.DateTime {ticks_per_day = DWARF_TICKS_PER_DAY} + local t = dt:monthsToTicks(1) + expect.eq(t, DWARF_TICKS_PER_DAY * dateT.DAYS_PER_MONTH) +end + +function test.isFullMoon() + local dt = dateT.DateTime {} + expect.false_(dt:setDayOfMonth(1, 1):isFullMoon()) + expect.true_(dt:setDayOfMonth(25, 1):isFullMoon()) +end + +function test.nextFullMoon() + local dt = dateT.DateTime {} + expect.eq(25, dt:setDayOfMonth(1, 1):nextFullMoon():getDayOfMonth()) + expect.eq(28, dt:setDayOfMonth(2, 12):nextFullMoon():getDayOfMonth()) +end + +function test.calendar_ops() + local dt_a = dateT.DwarfCalendar {year = 1, year_tick = DWARF_TICKS_PER_DAY, ticks_per_day = DWARF_TICKS_PER_DAY} + local dt_b = dateT.DwarfCalendar {year = 1, year_tick = DWARF_TICKS_PER_DAY + 1, ticks_per_day = DWARF_TICKS_PER_DAY} + local dt = dt_b - dt_a + expect.eq(dt.year, 0) + expect.eq(dt.year_tick, 1) + + dt = dt_a + dt_b + expect.eq(dt.year, 2) + expect.eq(dt.year_tick, DWARF_TICKS_PER_DAY * 2 + 1) +end + +function test.dateTime_ops() + local dt_a = dateT.DateTime {year = 1, year_tick = DWARF_TICKS_PER_DAY, ticks_per_day = DWARF_TICKS_PER_DAY} + local dt_b = dateT.DateTime {year = 1, year_tick = DWARF_TICKS_PER_DAY + 1, ticks_per_day = DWARF_TICKS_PER_DAY} + local dt = dt_b - dt_a + expect.eq(dt.year, 0) + expect.eq(dt.year_tick, 1) + + dt = dt_a + dt_b + expect.eq(dt.year, 2) + expect.eq(dt.year_tick, DWARF_TICKS_PER_DAY * 2 + 1) +end + +function test.dateTime_getTime() + -- 1 hour = 7200 tick + -- 1 min = 120 tick + -- 1 sec = 2 tick + -- 7324 is like 1.99999999999 secs in ADV time but truncated due to rounding + local dt = dateT.DateTime {year_tick = 7325, ticks_per_day = dateT.ADVENTURE_TICKS_PER_DAY} + local h, m, s = dt:getTime() + expect.eq(1, h) + expect.eq(1, m) + expect.eq(2, s) +end + +function test.dateTime_now() + expect.eq('table', type(dateT.DateTime.now())) +end + +function test.toDuration() + local dt = dateT.DateTime {} + expect.eq('table', type(dt:toDuration())) +end + +function test.duration_getTicks() + local d = dateT.Duration {year_tick = 1} + expect.eq(1, d:getTicks()) +end + +function test.duration_getDays() + local d = dateT.Duration {year = 1, year_tick = DWARF_TICKS_PER_DAY + 1, ticks_per_day = DWARF_TICKS_PER_DAY} + local days, ticks = d:getDays() + expect.eq(days, dateT.DAYS_PER_YEAR + 1) + expect.eq(ticks, 1) +end + +function test.duration_getMonths() + local d = dateT.Duration {year = 1, year_tick = DWARF_TICKS_PER_DAY + 1, ticks_per_day = DWARF_TICKS_PER_DAY} + local months, ticks = d:getMonths() + expect.eq(months, dateT.MONTHS_PER_YEAR) + expect.eq(ticks, DWARF_TICKS_PER_DAY + 1) +end + +function test.duration_getYearsMonthsDays() + local d = dateT.Duration {year = 1, year_tick = (DWARF_TICKS_PER_DAY * dateT.DAYS_PER_MONTH) + DWARF_TICKS_PER_DAY + 1, + ticks_per_day = DWARF_TICKS_PER_DAY} + local y, m, d, t = d:getYearsMonthsDays() + expect.eq(y, 1) + expect.eq(m, 1) + expect.eq(d, 1) + expect.eq(t, 1) +end + +function test.duration_ops() + local dt_a = dateT.Duration {year = 1, year_tick = DWARF_TICKS_PER_DAY, ticks_per_day = DWARF_TICKS_PER_DAY} + local dt_b = dateT.Duration {year = 1, year_tick = DWARF_TICKS_PER_DAY + 1, ticks_per_day = DWARF_TICKS_PER_DAY} + local dt = dt_b - dt_a + expect.eq(dt.year, 0) + expect.eq(dt.year_tick, 1) + + dt = dt_a + dt_b + expect.eq(dt.year, 2) + expect.eq(dt.year_tick, DWARF_TICKS_PER_DAY * 2 + 1) +end From b0b5e85439b8e2cbb51970aa83a4b748d1544c9e Mon Sep 17 00:00:00 2001 From: dhthwy <302825+dhthwy@users.noreply.github.com> Date: Wed, 8 Nov 2023 14:08:52 -0500 Subject: [PATCH 4/5] datetime: cleanup --- library/lua/datetime.lua | 47 +++++++++++------ test/library/datetime.lua | 107 ++++++++++++++++++++++++-------------- 2 files changed, 100 insertions(+), 54 deletions(-) diff --git a/library/lua/datetime.lua b/library/lua/datetime.lua index 65855de03f..b0cbd53061 100644 --- a/library/lua/datetime.lua +++ b/library/lua/datetime.lua @@ -116,12 +116,6 @@ function DwarfCalendar:addYears(years) return self end --- should this be named getYear()? --- returns an integer pair: (year), (year tick count) -function DwarfCalendar:getYears() - return self.year, self.year_tick -end - function DwarfCalendar:setDayOfMonth(day, month) month = month or self:getMonth() -- zero based when converting to ticks @@ -129,6 +123,12 @@ function DwarfCalendar:setDayOfMonth(day, month) return self end +-- should this be named getYear()? +-- returns an integer pair: (year), (year tick count) +function DwarfCalendar:getYears() + return self.year, self.year_tick +end + -- returns an integer pair: (day of month starting from 1), (day tick count) function DwarfCalendar:getDayOfMonth() return self:ticksToDayOfMonth() + 1, self:getDayTicks() @@ -194,7 +194,9 @@ function DwarfCalendar:isFullMoon() end function DwarfCalendar:nextFullMoon() - local dateT = DateTime{ year = self.year, year_tick = self.year_tick } + local dateT = DateTime{ year = self.year, + year_tick = self.year_tick, + ticks_per_day = self.ticks_per_day } if (dateT:isFullMoon()) then dateT:addDays(1) end local cur_m = dateT:getMonth() @@ -243,13 +245,17 @@ end function DwarfCalendar:__add(other) if DEBUG then self:_debugOps(other) end -- normalize() handles adjustments to year and year_tick - return DwarfCalendar{ year = (self.year + other.year), year_tick = (self.year_tick + other.year_tick), ticks_per_day = self.ticks_per_day } + return DwarfCalendar{ year = (self.year + other.year), + year_tick = (self.year_tick + other.year_tick), + ticks_per_day = self.ticks_per_day } end function DwarfCalendar:__sub(other) if DEBUG then self:_debugOps(other) end -- normalize() handles adjustments to year and year_tick - return DwarfCalendar{ year = (self.year - other.year) , year_tick = (self.year_tick - other.year_tick), ticks_per_day = self.ticks_per_day } + return DwarfCalendar{ year = (self.year - other.year), + year_tick = (self.year_tick - other.year_tick), + ticks_per_day = self.ticks_per_day } end function DwarfCalendar:_debugOps(other) @@ -287,20 +293,26 @@ end -- where the caller provides the time unit specifiers -- i.e. getDuration/toDuration('ymd') or toDuration('y', 'm', 'd'), etc function DateTime:toDuration() - return Duration{ year = self.year, year_tick = self.year_tick, ticks_per_day = self.ticks_per_day } + return Duration{ year = self.year, + year_tick = self.year_tick, + ticks_per_day = self.ticks_per_day } end function DateTime:__add(other) if DEBUG then self:_debugOps(other) end -- normalize() handles adjustments to year and year_tick - return DateTime{ year = (self.year + other.year), year_tick = (self.year_tick + other.year_tick), ticks_per_day = self.ticks_per_day } + return DateTime{ year = (self.year + other.year), + year_tick = (self.year_tick + other.year_tick), + ticks_per_day = self.ticks_per_day } end -- might make sense to return a Duration here function DateTime:__sub(other) if DEBUG then self:_debugOps(other) end -- normalize() handles adjustments to year and year_tick - return DateTime{ year = (self.year - other.year) , year_tick = (self.year_tick - other.year_tick), ticks_per_day = self.ticks_per_day } + return DateTime{ year = (self.year - other.year), + year_tick = (self.year_tick - other.year_tick), + ticks_per_day = self.ticks_per_day } end function DateTime.now(game_mode) @@ -311,7 +323,8 @@ function DateTime.now(game_mode) -- Tick rate defaults to DWARF mode, we should set the tick rate here as well -- For a custom rate the caller can use setTickRate() or we can add a second -- optional parameter - return DateTime{ year = df.global.cur_year, year_tick = ticks }:setTickRate(game_mode) + return DateTime{ year = df.global.cur_year, + year_tick = ticks }:setTickRate(game_mode) end Duration = defclass(Duration, DwarfCalendar) @@ -343,13 +356,17 @@ end function Duration:__add(other) if DEBUG then self:_debugOps(other) end -- normalize() handles adjustments to year and year_tick - return Duration{ year = (self.year + other.year), year_tick = (self.year_tick + other.year_tick) } + return Duration{ year = (self.year + other.year), + year_tick = (self.year_tick + other.year_tick), + ticks_per_day = self.ticks_per_day } end function Duration:__sub(other) if DEBUG then self:_debugOps(other) end -- normalize() handles adjustments to year and year_tick - return Duration{ year = (self.year - other.year) , year_tick = (self.year_tick - other.year_tick) } + return Duration{ year = (self.year - other.year), + year_tick = (self.year_tick - other.year_tick), + ticks_per_day = self.ticks_per_day } end return _ENV diff --git a/test/library/datetime.lua b/test/library/datetime.lua index 3ee9ea0fe7..552e1ac798 100644 --- a/test/library/datetime.lua +++ b/test/library/datetime.lua @@ -3,6 +3,7 @@ config.target = 'core' local dateT = require('datetime') local DWARF_TICKS_PER_DAY = dateT.DWARF_TICKS_PER_DAY +local DWARF_TICKS_PER_MONTH = DWARF_TICKS_PER_DAY * dateT.DAYS_PER_MONTH function test.getOrdinalSuffix() expect.eq(dateT.getOrdinalSuffix(1), 'st') @@ -21,25 +22,25 @@ end function test.normalize() local year_ticks = DWARF_TICKS_PER_DAY * dateT.DAYS_PER_YEAR - local dt = dateT.DateTime {year = 1, year_tick = year_ticks+1} + local dt = dateT.DateTime{ year = 1, year_tick = year_ticks+1 } expect.eq(dt.year, 2) expect.eq(dt.year_tick, 1) - dt = dateT.DateTime {year = 2, year_tick = -1} + dt = dateT.DateTime{ year = 2, year_tick = -1 } expect.eq(dt.year, 1) expect.eq(dt.year_tick, year_ticks-1) end function test.setTickRate() - local dt = dateT.DateTime {} + local dt = dateT.DateTime{} dt:setTickRate(0) expect.eq(dt.ticks_per_day, DWARF_TICKS_PER_DAY) - expect.eq(dt.ticks_per_month, DWARF_TICKS_PER_DAY * dateT.DAYS_PER_MONTH) + expect.eq(dt.ticks_per_month, DWARF_TICKS_PER_MONTH) expect.eq(dt.ticks_per_year, DWARF_TICKS_PER_DAY * dateT.DAYS_PER_YEAR) dt:setTickRate(df.game_mode.DWARF) expect.eq(dt.ticks_per_day, DWARF_TICKS_PER_DAY) - expect.eq(dt.ticks_per_month, DWARF_TICKS_PER_DAY * dateT.DAYS_PER_MONTH) + expect.eq(dt.ticks_per_month, DWARF_TICKS_PER_MONTH) expect.eq(dt.ticks_per_year, DWARF_TICKS_PER_DAY * dateT.DAYS_PER_YEAR) dt:setTickRate(df.game_mode.ADVENTURE) @@ -54,7 +55,7 @@ function test.setTickRate() end function test.addTicks() - local dt = dateT.DateTime {year_tick = 0} + local dt = dateT.DateTime{ year_tick = 0 } dt:addTicks(1) expect.eq(dt.year_tick, 1) dt:addTicks(-1) @@ -62,7 +63,7 @@ function test.addTicks() end function test.addDays() - local dt = dateT.DateTime {year_tick = 0} + local dt = dateT.DateTime{ year_tick = 0 } dt:addDays(1) expect.eq(dt.year_tick, dt.ticks_per_day) dt:addDays(-1) @@ -70,7 +71,7 @@ function test.addDays() end function test.addMonths() - local dt = dateT.DateTime {year_tick = 0} + local dt = dateT.DateTime{ year_tick = 0 } dt:addMonths(1) expect.eq(dt.year_tick, dt.ticks_per_month) dt:addMonths(-1) @@ -78,7 +79,7 @@ function test.addMonths() end function test.addYears() - local dt = dateT.DateTime {year = 0} + local dt = dateT.DateTime{ year = 0 } dt:addYears(1) expect.eq(dt.year, 1) dt:addYears(-1) @@ -86,108 +87,122 @@ function test.addYears() end function test.getYears() - local dt = dateT.DateTime {year = 1, year_tick = 1} + local dt = dateT.DateTime{ year = 1, year_tick = 1 } local y, t = dt:getYears() expect.eq(y, 1) expect.eq(t, 1) end function test.setDayOfMonth() - local dt = dateT.DateTime {year_tick = 0} + local dt = dateT.DateTime{ year_tick = 0 } dt:setDayOfMonth(2, 1) expect.eq(dt.year_tick, dt.ticks_per_day) end function test.getDayOfMonth() - local dt = dateT.DateTime {year_tick = DWARF_TICKS_PER_DAY * dateT.DAYS_PER_MONTH + 1, ticks_per_day = DWARF_TICKS_PER_DAY} + local dt = dateT.DateTime{ year_tick = DWARF_TICKS_PER_MONTH + 1, + ticks_per_day = DWARF_TICKS_PER_DAY } local day, ticks = dt:getDayOfMonth() expect.eq(day, 1) expect.eq(ticks, 1) end function test.getDayOfMonthWithSuffix() - local dt = dateT.DateTime {year_tick = DWARF_TICKS_PER_DAY * dateT.DAYS_PER_MONTH + 1, ticks_per_day = DWARF_TICKS_PER_DAY} + local dt = dateT.DateTime{ year_tick = DWARF_TICKS_PER_MONTH + 1, + ticks_per_day = DWARF_TICKS_PER_DAY } local day = dt:getDayOfMonthWithSuffix() expect.eq(day, '1st') end function test.getDayOfYear() - local dt = dateT.DateTime {year_tick = DWARF_TICKS_PER_DAY * dateT.DAYS_PER_MONTH + 1, ticks_per_day = DWARF_TICKS_PER_DAY} + local dt = dateT.DateTime{ year_tick = DWARF_TICKS_PER_MONTH + 1, + ticks_per_day = DWARF_TICKS_PER_DAY } local day, ticks = dt:getDayOfYear() expect.eq(day, dateT.DAYS_PER_MONTH+1) expect.eq(ticks, 1) end function test.getMonth() - local dt = dateT.DateTime {year_tick = DWARF_TICKS_PER_DAY * dateT.DAYS_PER_MONTH + 1, ticks_per_day = DWARF_TICKS_PER_DAY} + local dt = dateT.DateTime{ year_tick = DWARF_TICKS_PER_MONTH + 1, + ticks_per_day = DWARF_TICKS_PER_DAY } local month, ticks = dt:getMonth() expect.eq(month, 2) expect.eq(ticks, 1) end function test.getNameOfMonth() - local dt = dateT.DateTime {year_tick = DWARF_TICKS_PER_DAY * dateT.DAYS_PER_MONTH + 1, ticks_per_day = DWARF_TICKS_PER_DAY} + local dt = dateT.DateTime{ year_tick = DWARF_TICKS_PER_MONTH + 1, + ticks_per_day = DWARF_TICKS_PER_DAY } local name = dt:getNameOfMonth() expect.eq(name, 'Slate') end function test.ticksToDays() - local dt = dateT.DateTime {year_tick = DWARF_TICKS_PER_DAY * dateT.DAYS_PER_MONTH + 1, ticks_per_day = DWARF_TICKS_PER_DAY} + local dt = dateT.DateTime{ year_tick = DWARF_TICKS_PER_MONTH + 1, + ticks_per_day = DWARF_TICKS_PER_DAY } local days = dt:ticksToDays() expect.eq(days, dateT.DAYS_PER_MONTH) end function test.ticksToDayOfMonth() - local dt = dateT.DateTime {year_tick = DWARF_TICKS_PER_DAY * dateT.DAYS_PER_MONTH + 1, ticks_per_day = DWARF_TICKS_PER_DAY} + local dt = dateT.DateTime{ year_tick = DWARF_TICKS_PER_MONTH + 1, + ticks_per_day = DWARF_TICKS_PER_DAY } local days = dt:ticksToDayOfMonth() expect.eq(days, 0) end function test.ticksToMonths() - local dt = dateT.DateTime {year_tick = DWARF_TICKS_PER_DAY * dateT.DAYS_PER_MONTH + 1, ticks_per_day = DWARF_TICKS_PER_DAY} + local dt = dateT.DateTime{ year_tick = DWARF_TICKS_PER_MONTH + 1, + ticks_per_day = DWARF_TICKS_PER_DAY } local m = dt:ticksToMonths() expect.eq(m, 1) end function test.getDayTicks() - local dt = dateT.DateTime {year_tick = DWARF_TICKS_PER_DAY * dateT.DAYS_PER_MONTH + 1, ticks_per_day = DWARF_TICKS_PER_DAY} + local dt = dateT.DateTime{ year_tick = DWARF_TICKS_PER_MONTH + 1, + ticks_per_day = DWARF_TICKS_PER_DAY } local t = dt:getDayTicks() expect.eq(t, 1) end function test.getMonthTicks() - local dt = dateT.DateTime {year_tick = DWARF_TICKS_PER_DAY * dateT.DAYS_PER_MONTH + 1, ticks_per_day = DWARF_TICKS_PER_DAY} + local dt = dateT.DateTime{ year_tick = DWARF_TICKS_PER_MONTH + 1, + ticks_per_day = DWARF_TICKS_PER_DAY } local t = dt:getMonthTicks() expect.eq(t, 1) end function test.daysToTicks() - local dt = dateT.DateTime {ticks_per_day = DWARF_TICKS_PER_DAY} + local dt = dateT.DateTime{ ticks_per_day = DWARF_TICKS_PER_DAY } local t = dt:daysToTicks(1) expect.eq(t, DWARF_TICKS_PER_DAY) end function test.monthsToTicks() - local dt = dateT.DateTime {ticks_per_day = DWARF_TICKS_PER_DAY} + local dt = dateT.DateTime{ ticks_per_day = DWARF_TICKS_PER_DAY } local t = dt:monthsToTicks(1) - expect.eq(t, DWARF_TICKS_PER_DAY * dateT.DAYS_PER_MONTH) + expect.eq(t, DWARF_TICKS_PER_MONTH) end function test.isFullMoon() - local dt = dateT.DateTime {} + local dt = dateT.DateTime{} expect.false_(dt:setDayOfMonth(1, 1):isFullMoon()) expect.true_(dt:setDayOfMonth(25, 1):isFullMoon()) end function test.nextFullMoon() - local dt = dateT.DateTime {} + local dt = dateT.DateTime{} expect.eq(25, dt:setDayOfMonth(1, 1):nextFullMoon():getDayOfMonth()) expect.eq(28, dt:setDayOfMonth(2, 12):nextFullMoon():getDayOfMonth()) end function test.calendar_ops() - local dt_a = dateT.DwarfCalendar {year = 1, year_tick = DWARF_TICKS_PER_DAY, ticks_per_day = DWARF_TICKS_PER_DAY} - local dt_b = dateT.DwarfCalendar {year = 1, year_tick = DWARF_TICKS_PER_DAY + 1, ticks_per_day = DWARF_TICKS_PER_DAY} + local dt_a = dateT.DwarfCalendar{ year = 1, + year_tick = DWARF_TICKS_PER_DAY, + ticks_per_day = DWARF_TICKS_PER_DAY } + local dt_b = dateT.DwarfCalendar{ year = 1, + year_tick = DWARF_TICKS_PER_DAY + 1, + ticks_per_day = DWARF_TICKS_PER_DAY } local dt = dt_b - dt_a expect.eq(dt.year, 0) expect.eq(dt.year_tick, 1) @@ -198,8 +213,12 @@ function test.calendar_ops() end function test.dateTime_ops() - local dt_a = dateT.DateTime {year = 1, year_tick = DWARF_TICKS_PER_DAY, ticks_per_day = DWARF_TICKS_PER_DAY} - local dt_b = dateT.DateTime {year = 1, year_tick = DWARF_TICKS_PER_DAY + 1, ticks_per_day = DWARF_TICKS_PER_DAY} + local dt_a = dateT.DateTime{ year = 1, + year_tick = DWARF_TICKS_PER_DAY, + ticks_per_day = DWARF_TICKS_PER_DAY } + local dt_b = dateT.DateTime{ year = 1, + year_tick = DWARF_TICKS_PER_DAY + 1, + ticks_per_day = DWARF_TICKS_PER_DAY } local dt = dt_b - dt_a expect.eq(dt.year, 0) expect.eq(dt.year_tick, 1) @@ -214,7 +233,8 @@ function test.dateTime_getTime() -- 1 min = 120 tick -- 1 sec = 2 tick -- 7324 is like 1.99999999999 secs in ADV time but truncated due to rounding - local dt = dateT.DateTime {year_tick = 7325, ticks_per_day = dateT.ADVENTURE_TICKS_PER_DAY} + local dt = dateT.DateTime{ year_tick = 7325, + ticks_per_day = dateT.ADVENTURE_TICKS_PER_DAY } local h, m, s = dt:getTime() expect.eq(1, h) expect.eq(1, m) @@ -226,32 +246,37 @@ function test.dateTime_now() end function test.toDuration() - local dt = dateT.DateTime {} + local dt = dateT.DateTime{} expect.eq('table', type(dt:toDuration())) end function test.duration_getTicks() - local d = dateT.Duration {year_tick = 1} + local d = dateT.Duration{ year_tick = 1 } expect.eq(1, d:getTicks()) end function test.duration_getDays() - local d = dateT.Duration {year = 1, year_tick = DWARF_TICKS_PER_DAY + 1, ticks_per_day = DWARF_TICKS_PER_DAY} + local d = dateT.Duration{ year = 1, + year_tick = DWARF_TICKS_PER_DAY + 1, + ticks_per_day = DWARF_TICKS_PER_DAY } local days, ticks = d:getDays() expect.eq(days, dateT.DAYS_PER_YEAR + 1) expect.eq(ticks, 1) end function test.duration_getMonths() - local d = dateT.Duration {year = 1, year_tick = DWARF_TICKS_PER_DAY + 1, ticks_per_day = DWARF_TICKS_PER_DAY} + local d = dateT.Duration{ year = 1, + year_tick = DWARF_TICKS_PER_DAY + 1, + ticks_per_day = DWARF_TICKS_PER_DAY } local months, ticks = d:getMonths() expect.eq(months, dateT.MONTHS_PER_YEAR) expect.eq(ticks, DWARF_TICKS_PER_DAY + 1) end function test.duration_getYearsMonthsDays() - local d = dateT.Duration {year = 1, year_tick = (DWARF_TICKS_PER_DAY * dateT.DAYS_PER_MONTH) + DWARF_TICKS_PER_DAY + 1, - ticks_per_day = DWARF_TICKS_PER_DAY} + local d = dateT.Duration{ year = 1, + year_tick = DWARF_TICKS_PER_MONTH + DWARF_TICKS_PER_DAY + 1, + ticks_per_day = DWARF_TICKS_PER_DAY } local y, m, d, t = d:getYearsMonthsDays() expect.eq(y, 1) expect.eq(m, 1) @@ -260,8 +285,12 @@ function test.duration_getYearsMonthsDays() end function test.duration_ops() - local dt_a = dateT.Duration {year = 1, year_tick = DWARF_TICKS_PER_DAY, ticks_per_day = DWARF_TICKS_PER_DAY} - local dt_b = dateT.Duration {year = 1, year_tick = DWARF_TICKS_PER_DAY + 1, ticks_per_day = DWARF_TICKS_PER_DAY} + local dt_a = dateT.Duration{ year = 1, + year_tick = DWARF_TICKS_PER_DAY, + ticks_per_day = DWARF_TICKS_PER_DAY } + local dt_b = dateT.Duration{ year = 1, + year_tick = DWARF_TICKS_PER_DAY + 1, + ticks_per_day = DWARF_TICKS_PER_DAY } local dt = dt_b - dt_a expect.eq(dt.year, 0) expect.eq(dt.year_tick, 1) From 683fdf8e56272dffd91d3bd276e5d59ab8962ed6 Mon Sep 17 00:00:00 2001 From: dhthwy <302825+dhthwy@users.noreply.github.com> Date: Tue, 7 May 2024 10:54:53 -0400 Subject: [PATCH 5/5] implement suggested changes --- library/lua/datetime.lua | 526 +++++++++++++++++++++++++-------------- 1 file changed, 346 insertions(+), 180 deletions(-) diff --git a/library/lua/datetime.lua b/library/lua/datetime.lua index b0cbd53061..1a77341de4 100644 --- a/library/lua/datetime.lua +++ b/library/lua/datetime.lua @@ -5,16 +5,7 @@ DAYS_PER_YEAR = 336 MONTHS_PER_YEAR = 12 DWARF_TICKS_PER_DAY = 1200 ---local DWARF_TICKS_PER_MONTH = DWARF_TICKS_PER_DAY * DAYS_PER_MONTH ---local DWARF_TICKS_PER_YEAR = DWARF_TICKS_PER_MONTH * MONTHS_PER_YEAR - ADVENTURE_TICKS_PER_DAY = 172800 ---local ADVENTURE_TICKS_PER_MONTH = ADVENTURE_TICKS_PER_DAY * DAYS_PER_MONTH ---local ADVENTURE_TICKS_PER_YEAR = ADVENTURE_TICKS_PER_MONTH * MONTHS_PER_YEAR - ---local TICKS_PER_DAY = DWARF_TICKS_PER_DAY ---local TICKS_PER_MONTH = DWARF_TICKS_PER_MONTH ---local TICKS_PER_YEAR = TICKS_PER_MONTH * MONTHS_PER_YEAR CALENDAR_MONTHS = { 'Granite', @@ -64,142 +55,128 @@ function getOrdinalSuffix(ordinal) return ({'st', 'nd', 'rd'})[rem % 10] or 'th' end -DwarfCalendar = defclass(DwarfCalendar) - -DwarfCalendar.ATTRS{ - year=0, - year_tick=0, - ticks_per_day=DWARF_TICKS_PER_DAY -} - -function DwarfCalendar:init() - self:setTickRate(self.ticks_per_day) - self:normalize() +-- returns months since the beginning of a year, starting from zero +local function monthOfYearTick(obj) + return obj.year_tick // obj.ticks_per_month end -function DwarfCalendar:setTickRate(ticks_per_day) - -- game_mode.DWARF and .ADVENTURE values are < 10 - -- too low for sane tick rates, so we can utilize em. - -- this might be useful if the caller wants to set by game mode - if (ticks_per_day == df.game_mode.DWARF) then - self.ticks_per_day = DWARF_TICKS_PER_DAY - elseif (ticks_per_day == df.game_mode.ADVENTURE) then - self.ticks_per_day = ADVENTURE_TICKS_PER_DAY - else - self.ticks_per_day = (ticks_per_day > 0) and ticks_per_day or DWARF_TICKS_PER_DAY - end - self.ticks_per_month = self.ticks_per_day * DAYS_PER_MONTH - self.ticks_per_year = self.ticks_per_day * DAYS_PER_YEAR - return self +-- returns ticks since the beginning of a month +local function monthTick(obj) + return obj.year_tick % obj.ticks_per_month end -function DwarfCalendar:addTicks(ticks) - self.year_tick = self.year_tick + ticks - self:normalize() - return self +-- returns days since beginning of a year, starting from zero +local function dayOfYearTick(obj) + return obj.year_tick // obj.ticks_per_day end -function DwarfCalendar:addDays(days) - self.year_tick = self.year_tick + self:daysToTicks(days) - self:normalize() - return self +-- returns days since the beginning of a month, starting from zero +local function dayOfMonthTick(obj) + return dayOfYearTick(obj) % obj.calendar.daysPerMonth() end -function DwarfCalendar:addMonths(months) - self.year_tick = self.year_tick + self:monthsToTicks(months) - self:normalize() - return self +local function daysToTicks(obj, days) + return days * obj.ticks_per_day end -function DwarfCalendar:addYears(years) - self.year = self.year + years - return self +local function monthsToTicks(obj, months) + return months * obj.ticks_per_month end -function DwarfCalendar:setDayOfMonth(day, month) - month = month or self:getMonth() - -- zero based when converting to ticks - self.year_tick = self:monthsToTicks(month-1) + self:daysToTicks(day-1) - return self +-- returns ticks since the beginning of a day +local function dayTick(obj) + return obj.year_tick % obj.ticks_per_day end --- should this be named getYear()? --- returns an integer pair: (year), (year tick count) -function DwarfCalendar:getYears() - return self.year, self.year_tick +local function normalize(obj) + if (obj.year_tick > obj.ticks_per_year) then + obj.year = obj.year + (obj.year_tick // obj.ticks_per_year) + obj.year_tick = obj.year_tick % obj.ticks_per_year + elseif (obj.year_tick < 0) then + -- going backwards in time, subtract year by at least one. + obj.year = obj.year - math.max(1, math.abs(obj.year_tick) // obj.ticks_per_year) + -- porting note: Lua's modulo operator applies floor division, + -- hence year_tick will always be positive after assignment + -- equivalent to: year_tick - (TICKS_PER_YEAR * (year_tick // TICKS_PER_YEAR)) + obj.year_tick = obj.year_tick % obj.ticks_per_year + end + return obj end --- returns an integer pair: (day of month starting from 1), (day tick count) -function DwarfCalendar:getDayOfMonth() - return self:ticksToDayOfMonth() + 1, self:getDayTicks() -end +-- sets rate at which time passes +local function setTickRate(obj) + if (obj.mode == df.game_mode.ADVENTURE) then + obj.ticks_per_day = ADVENTURE_TICKS_PER_DAY + else + obj.ticks_per_day = DWARF_TICKS_PER_DAY + end --- returns a string in ordinal form (e.g. 1st, 12th, 22nd, 101st, 111th, 133rd) -function DwarfCalendar:getDayOfMonthWithSuffix() - local d = self:getDayOfMonth() - return tostring(d)..getOrdinalSuffix(d) + obj.ticks_per_month = obj.ticks_per_day * DwarfCalendar.daysPerMonth() + obj.ticks_per_year = obj.ticks_per_day * DwarfCalendar.daysPerYear() + return obj end --- returns an integer pair: (current day of year, from 1), (day tick count) -function DwarfCalendar:getDayOfYear() - return self:ticksToDays() + 1, self:getDayTicks() +local function addTicks(obj, ticks) + ticks = tonumber(ticks) + if not ticks then qerror('ticks must be a number') end + obj.year_tick = obj.year_tick + ticks + return normalize(obj) end --- returns an integer pair: (current month of the year, from 1), (month tick count) -function DwarfCalendar:getMonth() - return self:ticksToMonths() + 1, self:getMonthTicks() +local function addDays(obj, days) + days = tonumber(days) + if not days then qerror('days must be a number') end + obj.year_tick = obj.year_tick + daysToTicks(obj, days) + return normalize(obj) end --- returns a string of the current month of the year -function DwarfCalendar:getNameOfMonth() - return CALENDAR_MONTHS[self:getMonth()] or error("bad index?") +local function addMonths(obj, months) + months = tonumber(months) + if not months then qerror('months must be a number') end + obj.year_tick = obj.year_tick + monthsToTicks(obj, months) + return normalize(obj) end --- returns days since beginning of a year, starting from zero -function DwarfCalendar:ticksToDays() - return self.year_tick // self.ticks_per_day +local function addYears(obj, years) + years = tonumber(years) + if not years then qerror('years must be a number') end + obj.year = obj.year + years + return obj end --- returns days since the beginning of a month, starting from zero -function DwarfCalendar:ticksToDayOfMonth() - return self:ticksToDays() % DAYS_PER_MONTH -end +---------------------------- +------ DwarfCalendar ------- +-- A very simple calendar -- +---------------------------- --- returns months since the beginning of a year, starting from zero -function DwarfCalendar:ticksToMonths() - return self.year_tick // self.ticks_per_month -end +DwarfCalendar = defclass(DwarfCalendar) --- returns ticks since the beginning of a day -function DwarfCalendar:getDayTicks() - return self.year_tick % self.ticks_per_day +function DwarfCalendar.daysPerMonth() + return DAYS_PER_MONTH end --- returns ticks since the beginning of a month -function DwarfCalendar:getMonthTicks() - return self.year_tick % self.ticks_per_month +function DwarfCalendar.daysPerYear() + return DAYS_PER_YEAR end -function DwarfCalendar:daysToTicks(days) - return days * self.ticks_per_day +function DwarfCalendar.monthsPerYear() + return MONTHS_PER_YEAR end -function DwarfCalendar:monthsToTicks(months) - return months * self.ticks_per_month +function DwarfCalendar.months() + return CALENDAR_MONTHS end -function DwarfCalendar:isFullMoon() - return (self:getMonth() == CALENDAR_FULLMOON_MAP[self:getDayOfMonth()]) +function DwarfCalendar.isFullMoon(dateTime) + return (dateTime:month() == CALENDAR_FULLMOON_MAP[dateTime:dayOfMonth()]) and true or false end -function DwarfCalendar:nextFullMoon() - local dateT = DateTime{ year = self.year, - year_tick = self.year_tick, - ticks_per_day = self.ticks_per_day } - if (dateT:isFullMoon()) then dateT:addDays(1) end +function DwarfCalendar.nextFullMoon(dateTime) + local nextDateT = DateTime.newFrom(dateTime) + if (DwarfCalendar.isFullMoon(nextDateT)) then nextDateT:addDays(1) end - local cur_m = dateT:getMonth() + local cur_m = nextDateT:month() local fm = { 25, 23, @@ -215,104 +192,234 @@ function DwarfCalendar:nextFullMoon() 2, 28 } - if (dateT:getDayOfMonth() < fm[cur_m]) then - dateT:setDayOfMonth(fm[cur_m], cur_m) + if (nextDateT:dayOfMonth() < fm[cur_m]) then + nextDateT:setDayOfMonth(fm[cur_m], cur_m) else -- Next full moon is on the next month -- or next year if addDays() rolled us over. -- Obsidian is special since it has 2 full moons -- also handles the case when Obsidian day is between 2 and 28 exclusive - dateT:setDayOfMonth(fm[cur_m+1], cur_m) + nextDateT:setDayOfMonth(fm[cur_m+1], cur_m) end - return dateT + return nextDateT end -function DwarfCalendar:normalize() - if (self.year_tick > self.ticks_per_year) then - self.year = self.year + (self.year_tick // self.ticks_per_year) - self.year_tick = self.year_tick % self.ticks_per_year - elseif (self.year_tick < 0) then - -- going backwards in time, subtract year by at least one. - self.year = self.year - math.max(1, math.abs(self.year_tick) // self.ticks_per_year) - -- Lua's modulo operator applies floor division, - -- hence year_tick will always be positive after assignment - -- equivalent to: year_tick - (TICKS_PER_YEAR * (year_tick // TICKS_PER_YEAR)) - self.year_tick = self.year_tick % self.ticks_per_year + +local DATETIME_NAME = "datetime" +local DURATION_NAME = "duration" + +-------------- +-- DateTime -- +-------------- + +-- Supports calendars with the property that +-- each month has the same number of days + +DateTime = defclass(DateTime) + +DateTime.ATTRS{ + year=0, + year_tick=0, + mode=df.game_mode.DWARF +} + +function DateTime:init() + self._name = DATETIME_NAME + setTickRate(self) + normalize(self) +end + +-- returns an integer pair: (year), (year tick count) +function DateTime:getYear() + return self.year, self.year_tick +end + +function DateTime:setYear(year) + year = tonumber(year) + if not year then qerror('year must be a number') end + self.year = year + return self +end + +function DateTime:setYearTick(ticks) + ticks = tonumber(ticks) + if not ticks then qerror('ticks must be a number') end + self.year_tick = ticks + return normalize(self) +end + +-- returns an integer pair: (current day of year, from 1), (day tick count) +function DateTime:dayOfYear() + return dayOfYearTick(self) + 1, dayTick(self) +end + +function DateTime:setDayOfYear(day) + day = tonumber(day) + if not day or (day < 1 or day > DwarfCalendar.daysPerYear()) + then qerror('day must be between 1 and %d, inclusive'):format(DwarfCalendar.daysPerYear()) end + self.year_tick = daysToTicks(self, day-1) + return self end -function DwarfCalendar:__add(other) - if DEBUG then self:_debugOps(other) end - -- normalize() handles adjustments to year and year_tick - return DwarfCalendar{ year = (self.year + other.year), - year_tick = (self.year_tick + other.year_tick), - ticks_per_day = self.ticks_per_day } +-- returns an integer pair: (day of month starting from 1), (day tick count) +function DateTime:dayOfMonth() + return dayOfMonthTick(self) + 1, dayTick(self) end -function DwarfCalendar:__sub(other) - if DEBUG then self:_debugOps(other) end - -- normalize() handles adjustments to year and year_tick - return DwarfCalendar{ year = (self.year - other.year), - year_tick = (self.year_tick - other.year_tick), - ticks_per_day = self.ticks_per_day } +function DateTime:setDayOfMonth(day, month) + day = tonumber(day) + if not day or (day < 1 or day > self.calendar.daysPerMonth()) + then qerror('day must be between 1 and %d, inclusive'):format(self.calendar.daysPerMonth()) + end + + month = tonumber(month) + if not month then month = self:month() + elseif (month < 1 or month > self.calendar.monthsPerYear()) then + qerror('month must be between 1 and %d, inclusive'):format(self.calendar.monthsPerYear()) + end + -- zero based when converting to ticks + self.year_tick = monthsToTicks(self, month-1) + daysToTicks(self, day-1) + return self +end + +-- returns a string in ordinal form (e.g. 1st, 12th, 22nd, 101st, 111th, 133rd) +function DateTime:dayOfMonthWithSuffix() + local d = self:dayOfMonth() + return tostring(d)..getOrdinalSuffix(d) +end + +-- returns an integer pair: (current month of the year, from 1), (month tick count) +function DateTime:month() + return monthOfYearTick(self) + 1, monthTick(self) end -function DwarfCalendar:_debugOps(other) - print('first: '..self.year,self.year_tick) - print('second: '..other.year,other.year_tick) +function DateTime:setMonth(month) + month = tonumber(month) + if not month or (month < 1 or month > self.calendar.monthsPerYear()) then + qerror('month must be between 1 and %d, inclusive'):format(self.calendar.monthsPerYear()) + end + self.year_tick = monthsToTicks(self, month-1) + return self +end + +-- returns a string of the current month of the year +function DateTime:nameOfMonth() + return DwarfCalendar.months()[self:month()] or error("bad index?") end -DateTime = defclass(DateTime, DwarfCalendar) +-- returns ticks since the beginning of a day +function DateTime:dayTick() + return dayTick(self) +end + +-- sets the current day tick count +function DateTime:setDayTick(ticks) + ticks = tonumber(ticks) + -- prevent rollover to next day + if not ticks or (ticks < 0 or ticks > self.ticks_per_day - 1) then + qerror('ticks must be between 0 and %d, inclusive'):format(self.ticks_per_day - 1) + end + self.year_tick = (self.year_tick - dayTick(self)) + ticks + return self +end + + +function DateTime:addTicks(ticks) + return addTicks(self, ticks) +end + +function DateTime:addDays(days) + return addDays(self, days) +end + +function DateTime:addMonths(months) + return addMonths(self, months) +end + +function DateTime:addYears(years) + return addYears(self, years) +end -- returns hours (24 hour format), minutes, seconds -function DateTime:getTime() +function DateTime:time() -- probably only useful in adv mode where a day is 144x longer - local h = self:getDayTicks() / (self.ticks_per_day / 24) + local h = dayTick(self) / (self.ticks_per_day / 24) local m = (h * 60) % 60 local s = (m * 60) % 60 -- return as integers, rounded down return h//1, m//1, s//1 end --- TODO: maybe add setTime() +function DateTime:setTime(hour, min, sec) + -- we're setting time of the current day, so we'll have to + -- ensure we're within bounds + hour = tonumber(hour) + if not hour or (hour < 0 or hour > 23) then + qerror('hour must be a number between 0 and 23, inclusive') end ---function DateTime:daysTo(other) --- local dateT = other - self --- return dateT.year * DAYS_PER_YEAR + dateT:ticksToDays() ---end --- maybe useful addition ---function DateTime:daysTo(other) --- return (other - self):toDuration().getDays() ---end + min = tonumber(min) + if not min or (min < 0 or min > 59) then + qerror('min must be a number between 0 and 59, inclusive') end --- Alternatively, instead of a Duration object, --- we can simply synthesize a table with --- key value pairs of years, months, days, ticks. --- If table, it could optionally take an argument or variadic --- where the caller provides the time unit specifiers --- i.e. getDuration/toDuration('ymd') or toDuration('y', 'm', 'd'), etc -function DateTime:toDuration() - return Duration{ year = self.year, - year_tick = self.year_tick, - ticks_per_day = self.ticks_per_day } + sec = tonumber(sec) + if not sec or (sec < 0 or sec > 59) then + qerror('sec must be a number between 0 and 59, inclusive') end + + local h = hour * (self.ticks_per_day / 24) + local m = min * (h / 60) + local s = sec * (m / 60) + return self:setDayTick((h + m + s)//1) end function DateTime:__add(other) - if DEBUG then self:_debugOps(other) end -- normalize() handles adjustments to year and year_tick return DateTime{ year = (self.year + other.year), year_tick = (self.year_tick + other.year_tick), - ticks_per_day = self.ticks_per_day } + mode = self.mode } end --- might make sense to return a Duration here function DateTime:__sub(other) - if DEBUG then self:_debugOps(other) end -- normalize() handles adjustments to year and year_tick - return DateTime{ year = (self.year - other.year), + if other._name == DURATION_NAME then + return DateTime{ year = (self.year - other.year), + year_tick = (self.year_tick - other.year_tick), + mode = self.mode } + end + + return Duration{ year = (self.year - other.year), year_tick = (self.year_tick - other.year_tick), - ticks_per_day = self.ticks_per_day } + mode = self.mode } +end + +function DateTime:_isEq(other) + return (self.year == other.year and self.year_tick == other.year_tick) + and true or false +end + +function DateTime:_isLt(other) + return (self.year < other.year or + (self.year == other.year and self.year_tick < other.year_tick)) + and true or false +end + +function DateTime:__eq(other) + return self:_isEq(other) +end + +function DateTime:__lt(other) + return self:_isLt(other) +end + +function DateTime:__le(other) + return (self:_isLt(other) or self:_isEq(other)) +end + +function DateTime.newFrom(other) + return DateTime{ year = other.year, + year_tick = other.year_tick, + mode = other.mode } end function DateTime.now(game_mode) @@ -320,28 +427,45 @@ function DateTime.now(game_mode) -- if game_mode is not given or not ADVENTURE then default to DWARF mode local ticks = (game_mode == df.game_mode.ADVENTURE) and (df.global.cur_year_tick_advmode) or (df.global.cur_year_tick) - -- Tick rate defaults to DWARF mode, we should set the tick rate here as well - -- For a custom rate the caller can use setTickRate() or we can add a second - -- optional parameter + return DateTime{ year = df.global.cur_year, - year_tick = ticks }:setTickRate(game_mode) + year_tick = ticks, + mode = game_mode } end -Duration = defclass(Duration, DwarfCalendar) +-------------- +-- Duration -- +-------------- + +Duration = defclass(Duration) + +Duration.ATTRS{ + year=0, + year_tick=0, + mode=df.game_mode.DWARF +} + +function Duration:init() + self._name = DURATION_NAME + setTickRate(self) + normalize(self) +end -- returns ticks since year zero -function Duration:getTicks() +function Duration:ticks() + -- Lua supports numbers up to 2^1024 + -- so in practice, we don't have to worry about overflow return self.year * self.ticks_per_year + self.year_tick end -- returns an integer pair: (days since year zero), (day tick count) -function Duration:getDays() - return self.year * DAYS_PER_YEAR + self:ticksToDays(), self:getDayTicks() +function Duration:days() + return self.year * DwarfCalendar.daysPerYear() + dayOfYearTick(self), dayTick(self) end -- returns an integer pair: (months since year zero), (month tick count) -function Duration:getMonths() - return self.year * MONTHS_PER_YEAR + self:ticksToMonths(), self:getMonthTicks() +function Duration:months() + return self.year * DwarfCalendar.monthsPerYear() + monthOfYearTick(self), monthTick(self) end -- returns parts of an elapsed time: @@ -349,24 +473,66 @@ end -- months, - since start of year -- days, - since start of month -- day tick count - since start of day -function Duration:getYearsMonthsDays() - return self.year, self:ticksToMonths(), self:ticksToDayOfMonth(), self:getDayTicks() +function Duration:yearsMonthsDays() + return self.year, monthOfYearTick(self), dayOfMonthTick(self), dayTick(self) +end + +function Duration:addTicks(ticks) + return addTicks(self, ticks) +end + +function Duration:addDays(days) + return addDays(self, days) +end + +function Duration:addMonths(months) + return addMonths(self, months) +end + +function Duration:addYears(years) + return addYears(self, years) +end + +function Duration.newFrom(other) + return Duration{ year = other.year, + year_tick = other.year_tick, + mode = other.mode } end function Duration:__add(other) - if DEBUG then self:_debugOps(other) end - -- normalize() handles adjustments to year and year_tick + if other._name == DATETIME_NAME then + return DateTime{ year = (self.year - other.year), + year_tick = (self.year_tick - other.year_tick), + mode = self.mode } + end + return Duration{ year = (self.year + other.year), year_tick = (self.year_tick + other.year_tick), - ticks_per_day = self.ticks_per_day } + mode = self.mode } end function Duration:__sub(other) - if DEBUG then self:_debugOps(other) end - -- normalize() handles adjustments to year and year_tick + if other._name == DATETIME_NAME then + return DateTime{ year = (self.year - other.year), + year_tick = (self.year_tick - other.year_tick), + mode = self.mode } + end + return Duration{ year = (self.year - other.year), year_tick = (self.year_tick - other.year_tick), - ticks_per_day = self.ticks_per_day } + mode = self.mode } +end + +function Duration:__eq(other) + return self:_isEq(other) +end + +function Duration:__lt(other) + return self:_isLt(other) +end + +function Duration:__le(other) + return (self:_isLt(other) or self:_isEq(other)) end return _ENV