I wanted to make an application that features a calendar but quickly noticed that playdate has no os.time()
or os.date()
Lua functions, making it impossible to calculate dates. I have attached my pure Lua code to showcase which methods are required to make that app.
Here's my code in pure Lua.
local Calendar = {}
local function clamp(value, min, max)
return math.max(math.min(tonumber(value), tonumber(max) or 1), tonumber(min) or 0)
end
function Calendar.isLeapYear(year)
return year % 4 == 0 and (year % 100 ~= 0 or year % 400 == 0)
end
function Calendar.isLightsaveDay(year, month, day)
return os.date("*t", os.time{year = year, month = month, day = day})["isdst"]
end
function Calendar.getWeekdayIndex(year, month, day)
local day_position = os.date("*t", os.time{year = year, month = month, day = day})["wday"] - 1 -- shift weekday indices to 0-6, starting with sunday at 0
if(day_position == 0) then day_position = 7 end -- move sunday from index 0 to 7, thus starting week at 1 with monday and ending it with 7 at sunday
return day_position
end
function Calendar.getWeeknames(locale)
return ({
en = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"},
de = {"Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag"},
ru = {"Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота", "Воскресенье"}
})[locale or "en"]
end
function Calendar.getMonthnames(locale)
return ({
en = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"},
de = {"Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"},
ru = {"Январь", "Февраль", "Март", "Апрель", "Май", "Июнь", "Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь"}
})[locale or "en"]
end
function Calendar.getMonthDaycount(year, month) -- based on code from http://lua-users.org/wiki/DayOfWeekAndDaysInMonthExample
return month == 2 and Calendar.isLeapYear(year) and 29 or ("\31\28\31\30\31\30\31\31\30\31\30\31"):byte(month)
end
function Calendar.getYearDaycount(year, month, day)
return tonumber(os.date("%j", os.time{year = year, month = month, day = day})) -- date format documentation at https://www.gammon.com.au/scripts/doc.php?lua=os.date
end
function Calendar.getWeekIndex(year, month, day) -- based on code from http://lua-users.org/wiki/WeekNumberInYear
local weekDayOffset = function(year)
local beginning_week = Calendar.getWeekdayIndex(year, 1, 1)
return beginning_week < 5
and beginning_week - 2 -- first day is week 1
or beginning_week - 9 -- first day is week 52 or 53
end
local elapsed_days = Calendar.getYearDaycount(year, month, day)
local day_of_year = elapsed_days + weekDayOffset(year)
if day_of_year < 0 then -- week of last year: decide if 52 or 53
day_of_year = elapsed_days + Calendar.getYearDaycount(year - 1, 12, 31) + weekDayOffset(year - 1)
end
local week_number = math.floor(day_of_year / 7) + 1
if(week_number == 53 and day_of_year > 0 and Calendar.getWeekdayIndex(year + 1, 1, 1) < 5) then -- day in week 1 is part of the next year
return 1
end
return week_number
end
function Calendar.getDate(year, month, day, locale)
day = clamp(day, 1, Calendar.getMonthDaycount(year, month)) -- consider leap year and cut overflowing days
local weekday = Calendar.getWeekdayIndex(year, month, day)
local weeknames = Calendar.getWeeknames(locale)
return {
locale = locale,
year = year,
year_daycount = Calendar.getYearDaycount(year, 12, 31),
year_weekcount = Calendar.getWeekIndex(year, 12, 31),
year_leaping = Calendar.isLeapYear(year),
month = month,
month_daycount = Calendar.getMonthDaycount(year, month),
month_name = Calendar.getMonthnames(locale)[month],
weekday = weekday,
weekday_name = weeknames[weekday],
week_daycount = #weeknames,
week_firstday = weeknames[1],
calendar_week = Calendar.getWeekIndex(year, month, day),
day = day,
day_lightsaving = Calendar.isLightsaveDay(year, month, day)
}
end
return Calendar
I could pre-render that data for the next couple of years, but that seems to be a rather stupid workaround as the app would need to be re-compiled every now and then.