mirror of https://github.com/koreader/koreader
Add datetime.lua
Move date and time related functions from util.lua (and the statistics plugin) to a new datetime.lua.reviewable/pr9876/r2
parent
2e5284051a
commit
192a243b4d
@ -0,0 +1,296 @@
|
||||
--[[--
|
||||
This module contains date translations and helper functions for the KOReader frontend.
|
||||
]]
|
||||
|
||||
local BaseUtil = require("ffi/util")
|
||||
local _ = require("gettext")
|
||||
local C_ = _.pgettext
|
||||
local T = BaseUtil.template
|
||||
|
||||
local datetime = {}
|
||||
|
||||
datetime.weekDays = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" } -- in Lua wday order
|
||||
|
||||
datetime.shortMonthTranslation = {
|
||||
["Jan"] = _("Jan"),
|
||||
["Feb"] = _("Feb"),
|
||||
["Mar"] = _("Mar"),
|
||||
["Apr"] = _("Apr"),
|
||||
["May"] = _("May"),
|
||||
["Jun"] = _("Jun"),
|
||||
["Jul"] = _("Jul"),
|
||||
["Aug"] = _("Aug"),
|
||||
["Sep"] = _("Sep"),
|
||||
["Oct"] = _("Oct"),
|
||||
["Nov"] = _("Nov"),
|
||||
["Dec"] = _("Dec"),
|
||||
}
|
||||
|
||||
datetime.longMonthTranslation = {
|
||||
["January"] = _("January"),
|
||||
["February"] = _("February"),
|
||||
["March"] = _("March"),
|
||||
["April"] = _("April"),
|
||||
["May"] = _("May"),
|
||||
["June"] = _("June"),
|
||||
["July"] = _("July"),
|
||||
["August"] = _("August"),
|
||||
["September"] = _("September"),
|
||||
["October"] = _("October"),
|
||||
["November"] = _("November"),
|
||||
["December"] = _("December"),
|
||||
}
|
||||
|
||||
datetime.shortDayOfWeekTranslation = {
|
||||
["Mon"] = _("Mon"),
|
||||
["Tue"] = _("Tue"),
|
||||
["Wed"] = _("Wed"),
|
||||
["Thu"] = _("Thu"),
|
||||
["Fri"] = _("Fri"),
|
||||
["Sat"] = _("Sat"),
|
||||
["Sun"] = _("Sun"),
|
||||
}
|
||||
|
||||
datetime.shortDayOfWeekToLongTranslation = {
|
||||
["Mon"] = _("Monday"),
|
||||
["Tue"] = _("Tuesday"),
|
||||
["Wed"] = _("Wednesday"),
|
||||
["Thu"] = _("Thursday"),
|
||||
["Fri"] = _("Friday"),
|
||||
["Sat"] = _("Saturday"),
|
||||
["Sun"] = _("Sunday"),
|
||||
}
|
||||
|
||||
--[[--
|
||||
Converts seconds to a clock string.
|
||||
|
||||
Source: <a href="https://gist.github.com/jesseadams/791673">https://gist.github.com/jesseadams/791673</a>
|
||||
]]
|
||||
---- @int seconds number of seconds
|
||||
---- @bool withoutSeconds if true 00:00, if false 00:00:00
|
||||
---- @treturn string clock string in the form of 00:00 or 00:00:00
|
||||
function datetime.secondsToClock(seconds, withoutSeconds, withDays)
|
||||
seconds = tonumber(seconds)
|
||||
if not seconds then
|
||||
if withoutSeconds then
|
||||
return "--:--"
|
||||
else
|
||||
return "--:--:--"
|
||||
end
|
||||
elseif seconds == 0 or seconds ~= seconds then
|
||||
if withoutSeconds then
|
||||
return "00:00"
|
||||
else
|
||||
return "00:00:00"
|
||||
end
|
||||
else
|
||||
local round = withoutSeconds and require("optmath").round or function(n) return n end
|
||||
local days = "0"
|
||||
local hours
|
||||
if withDays then
|
||||
days = string.format("%d", seconds * (1/(24*3600))) -- implicit math.floor for string.format
|
||||
hours = string.format("%02d", (seconds * (1/3600)) % 24)
|
||||
else
|
||||
hours = string.format("%02d", seconds * (1/3600))
|
||||
end
|
||||
local mins = string.format("%02d", round(seconds % 3600 * (1/60)))
|
||||
if withoutSeconds then
|
||||
if mins == "60" then
|
||||
-- Can only happen because of rounding, which only happens if withoutSeconds...
|
||||
mins = string.format("%02d", 0)
|
||||
hours = string.format("%02d", hours + 1)
|
||||
end
|
||||
return (days ~= "0" and (days .. "d") or "") .. hours .. ":" .. mins
|
||||
else
|
||||
local secs = string.format("%02d", seconds % 60)
|
||||
return (days ~= "0" and (days .. "d") or "") .. hours .. ":" .. mins .. ":" .. secs
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Converts seconds to a period of time string.
|
||||
---- @int seconds number of seconds
|
||||
---- @bool withoutSeconds if true 1h30', if false 1h30'10"
|
||||
---- @bool hmsFormat, if true format 1h30m10s
|
||||
---- @bool withDays, if true format 1d12h30m10s
|
||||
---- @treturn string clock string in the form of 1h30'10" or 1h30m10s
|
||||
function datetime.secondsToHClock(seconds, withoutSeconds, hmsFormat, withDays, compact)
|
||||
local SECONDS_SYMBOL = "\""
|
||||
seconds = tonumber(seconds)
|
||||
if seconds == 0 then
|
||||
if withoutSeconds then
|
||||
if hmsFormat then
|
||||
return T(_("%1m"), "0")
|
||||
else
|
||||
return "0'"
|
||||
end
|
||||
else
|
||||
if hmsFormat then
|
||||
return T(C_("Time", "%1s"), "0")
|
||||
else
|
||||
return "0" .. SECONDS_SYMBOL
|
||||
end
|
||||
end
|
||||
elseif seconds < 60 then
|
||||
if withoutSeconds and seconds < 30 then
|
||||
if hmsFormat then
|
||||
return T(C_("Time", "%1m"), "0")
|
||||
else
|
||||
return "0'"
|
||||
end
|
||||
elseif withoutSeconds and seconds >= 30 then
|
||||
if hmsFormat then
|
||||
return T(C_("Time", "%1m"), "1")
|
||||
else
|
||||
return "1'"
|
||||
end
|
||||
else
|
||||
if hmsFormat then
|
||||
if compact then
|
||||
return T(C_("Time", "%1s"), string.format("%d", seconds))
|
||||
else
|
||||
return T(C_("Time", "%1m%2s"), "0", string.format("%02d", seconds))
|
||||
end
|
||||
else
|
||||
if compact then
|
||||
return string.format("%d", seconds) .. SECONDS_SYMBOL
|
||||
else
|
||||
return "0'" .. string.format("%02d", seconds) .. SECONDS_SYMBOL
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
local time_string = datetime.secondsToClock(seconds, withoutSeconds, withDays)
|
||||
if withoutSeconds then
|
||||
time_string = time_string .. ":"
|
||||
end
|
||||
time_string = time_string:gsub(":", C_("Time", "h"), 1)
|
||||
time_string = time_string:gsub(":", C_("Time", "m"), 1)
|
||||
time_string = time_string:gsub("^00" .. C_("Time", "h"), "") -- delete leading "00h"
|
||||
time_string = time_string:gsub("^00" .. C_("Time", "m"), "") -- delete leading "00m"
|
||||
if time_string:find("^0%d") then
|
||||
time_string = time_string:gsub("^0", "") -- delete leading "0"
|
||||
end
|
||||
if withoutSeconds and time_string == "" then
|
||||
time_string = "0" .. C_("Time", "m")
|
||||
end
|
||||
|
||||
if hmsFormat then
|
||||
return withoutSeconds and time_string or (time_string .. C_("Time", "s"))
|
||||
else
|
||||
time_string = time_string:gsub(C_("Time", "m"), "'") -- replace m with '
|
||||
return withoutSeconds and time_string or (time_string .. SECONDS_SYMBOL)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Converts seconds to a clock type (classic or modern), based on the given format preference
|
||||
--- "Classic" format calls secondsToClock, and "Modern" format calls secondsToHClock
|
||||
---- @string Either "modern" for 1h30'10" or "classic" for 1:30:10
|
||||
---- @bool withoutSeconds if true 1h30' or 1h30m, if false 1h30'10" or 1h30m10s
|
||||
---- @bool hmsFormat, modern format only, if true format 1h30m or 1h30m10s
|
||||
---- @bool withDays, if hours>=24 include days in clock string 1d12h10m10s
|
||||
---- @bool compact, if set removes all leading zeros (incl. units if necessary)
|
||||
---- @treturn string clock string in the specific format of 1h30', 1h30'10" resp. 1h30m, 1h30m10s
|
||||
function datetime.secondsToClockDuration(format, seconds, withoutSeconds, hmsFormat, withDays, compact)
|
||||
if format == "modern" then
|
||||
return datetime.secondsToHClock(seconds, withoutSeconds, hmsFormat, withDays, compact)
|
||||
else
|
||||
-- Assume "classic" to give safe default
|
||||
return datetime.secondsToClock(seconds, withoutSeconds, withDays)
|
||||
end
|
||||
end
|
||||
|
||||
if jit.os == "Windows" then
|
||||
--- Converts timestamp to an hour string
|
||||
---- @int seconds number of seconds
|
||||
---- @bool twelve_hour_clock
|
||||
---- @treturn string hour string
|
||||
---- @note: The MS CRT doesn't support either %l & %k, or the - format modifier (as they're not technically C99 or POSIX).
|
||||
---- They are otherwise supported on Linux, BSD & Bionic, so, just special-case Windows...
|
||||
---- We *could* arguably feed the os.date output to gsub("^0(%d)(.*)$", "%1%2"), but, while unlikely,
|
||||
---- it's conceivable that a translator would put something other that the hour at the front of the string ;).
|
||||
function datetime.secondsToHour(seconds, twelve_hour_clock)
|
||||
if twelve_hour_clock then
|
||||
if os.date("%p", seconds) == "AM" then
|
||||
-- @translators This is the time in the morning using a 12-hour clock (%I is the hour, %M the minute).
|
||||
return os.date(_("%I:%M AM"), seconds)
|
||||
else
|
||||
-- @translators This is the time in the afternoon using a 12-hour clock (%I is the hour, %M the minute).
|
||||
return os.date(_("%I:%M PM"), seconds)
|
||||
end
|
||||
else
|
||||
-- @translators This is the time using a 24-hour clock (%H is the hour, %M the minute).
|
||||
return os.date(_("%H:%M"), seconds)
|
||||
end
|
||||
end
|
||||
else
|
||||
function datetime.secondsToHour(seconds, twelve_hour_clock, pad_with_spaces)
|
||||
if twelve_hour_clock then
|
||||
if os.date("%p", seconds) == "AM" then
|
||||
if pad_with_spaces then
|
||||
-- @translators This is the time in the morning using a 12-hour clock (%_I is the hour, %M the minute).
|
||||
return os.date(_("%_I:%M AM"), seconds)
|
||||
else
|
||||
-- @translators This is the time in the morning using a 12-hour clock (%-I is the hour, %M the minute).
|
||||
return os.date(_("%-I:%M AM"), seconds)
|
||||
end
|
||||
else
|
||||
if pad_with_spaces then
|
||||
-- @translators This is the time in the afternoon using a 12-hour clock (%_I is the hour, %M the minute).
|
||||
return os.date(_("%_I:%M PM"), seconds)
|
||||
else
|
||||
-- @translators This is the time in the afternoon using a 12-hour clock (%-I is the hour, %M the minute).
|
||||
return os.date(_("%-I:%M PM"), seconds)
|
||||
end
|
||||
end
|
||||
else
|
||||
if pad_with_spaces then
|
||||
-- @translators This is the time using a 24-hour clock (%_H is the hour, %M the minute).
|
||||
return os.date(_("%_H:%M"), seconds)
|
||||
else
|
||||
-- @translators This is the time using a 24-hour clock (%-H is the hour, %M the minute).
|
||||
return os.date(_("%-H:%M"), seconds)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Converts timestamp to a date string
|
||||
---- @int seconds number of seconds
|
||||
---- @use_locale if true allows to translate the date-time string, if false return "%Y-%m-%d time"
|
||||
---- @treturn string date string
|
||||
function datetime.secondsToDate(seconds, use_locale)
|
||||
seconds = seconds or os.time()
|
||||
if use_locale then
|
||||
local wday = os.date("%a", seconds)
|
||||
local month = os.date("%b", seconds)
|
||||
local day = os.date("%d", seconds)
|
||||
local year = os.date("%Y", seconds)
|
||||
|
||||
-- @translators Use the following placeholders in the desired order: %1 name of day, %2 name of month, %3 day, %4 year
|
||||
return T(C_("Date string", "%1 %2 %3 %4"),
|
||||
datetime.shortDayOfWeekTranslation[wday], datetime.shortMonthTranslation[month], day, year)
|
||||
else
|
||||
-- @translators This is the date (%Y is the year, %m the month, %d the day)
|
||||
return os.date(C_("Date string", "%Y-%m-%d"), seconds)
|
||||
end
|
||||
end
|
||||
|
||||
--- Converts timestamp to a date+time string
|
||||
---- @int seconds number of seconds
|
||||
---- @bool twelve_hour_clock
|
||||
---- @use_locale if true allows to translate the date-time string, if false return "%Y-%m-%d time"
|
||||
---- @treturn string date+time
|
||||
function datetime.secondsToDateTime(seconds, twelve_hour_clock, use_locale)
|
||||
seconds = seconds or os.time()
|
||||
local BD = require("ui/bidi")
|
||||
local date_string = datetime.secondsToDate(seconds, use_locale)
|
||||
local time_string = datetime.secondsToHour(seconds, twelve_hour_clock, not use_locale)
|
||||
|
||||
-- @translators Use the following placeholders in the desired order: %1 date, %2 time
|
||||
local message_text = T(C_("Date string", "%1 %2"), BD.wrap(date_string), BD.wrap(time_string))
|
||||
return message_text
|
||||
end
|
||||
|
||||
return datetime
|
@ -0,0 +1,188 @@
|
||||
describe("datetime module", function()
|
||||
local datetime
|
||||
setup(function()
|
||||
require("commonrequire")
|
||||
datetime = require("datetime")
|
||||
end)
|
||||
|
||||
describe("secondsToClock()", function()
|
||||
it("should convert seconds to 00:00 format", function()
|
||||
assert.is_equal("00:00",
|
||||
datetime.secondsToClock(0, true))
|
||||
assert.is_equal("00:01",
|
||||
datetime.secondsToClock(60, true))
|
||||
end)
|
||||
it("should round seconds to minutes in 00:00 format", function()
|
||||
assert.is_equal("00:01",
|
||||
datetime.secondsToClock(89, true))
|
||||
assert.is_equal("00:02",
|
||||
datetime.secondsToClock(90, true))
|
||||
assert.is_equal("00:02",
|
||||
datetime.secondsToClock(110, true))
|
||||
assert.is_equal("00:02",
|
||||
datetime.secondsToClock(120, true))
|
||||
assert.is_equal("01:00",
|
||||
datetime.secondsToClock(3600, true))
|
||||
assert.is_equal("01:00",
|
||||
datetime.secondsToClock(3599, true))
|
||||
assert.is_equal("01:00",
|
||||
datetime.secondsToClock(3570, true))
|
||||
assert.is_equal("00:59",
|
||||
datetime.secondsToClock(3569, true))
|
||||
end)
|
||||
it("should convert seconds to 00:00:00 format", function()
|
||||
assert.is_equal("00:00:00",
|
||||
datetime.secondsToClock(0))
|
||||
assert.is_equal("00:01:00",
|
||||
datetime.secondsToClock(60))
|
||||
assert.is_equal("00:01:29",
|
||||
datetime.secondsToClock(89))
|
||||
assert.is_equal("00:01:30",
|
||||
datetime.secondsToClock(90))
|
||||
assert.is_equal("00:01:50",
|
||||
datetime.secondsToClock(110))
|
||||
assert.is_equal("00:02:00",
|
||||
datetime.secondsToClock(120))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("secondsToHClock()", function()
|
||||
it("should convert seconds to 0'00'' format", function()
|
||||
assert.is_equal("0'",
|
||||
datetime.secondsToHClock(0, true))
|
||||
assert.is_equal("0'",
|
||||
datetime.secondsToHClock(29, true))
|
||||
assert.is_equal("1'",
|
||||
datetime.secondsToHClock(60, true))
|
||||
end)
|
||||
it("should round seconds to minutes in 0h00' format", function()
|
||||
assert.is_equal("1'",
|
||||
datetime.secondsToHClock(89, true))
|
||||
assert.is_equal("2'",
|
||||
datetime.secondsToHClock(90, true))
|
||||
assert.is_equal("2'",
|
||||
datetime.secondsToHClock(110, true))
|
||||
assert.is_equal("2'",
|
||||
datetime.secondsToHClock(120, true))
|
||||
assert.is_equal("1h00'",
|
||||
datetime.secondsToHClock(3600, true))
|
||||
assert.is_equal("1h00'",
|
||||
datetime.secondsToHClock(3599, true))
|
||||
assert.is_equal("1h00'",
|
||||
datetime.secondsToHClock(3570, true))
|
||||
assert.is_equal("59'",
|
||||
datetime.secondsToHClock(3569, true))
|
||||
assert.is_equal("10h01'",
|
||||
datetime.secondsToHClock(36060, true))
|
||||
end)
|
||||
it("should round seconds to minutes in 0h00m format", function()
|
||||
assert.is_equal("1m",
|
||||
datetime.secondsToHClock(89, true, true))
|
||||
assert.is_equal("2m",
|
||||
datetime.secondsToHClock(90, true, true))
|
||||
assert.is_equal("2m",
|
||||
datetime.secondsToHClock(110, true, true))
|
||||
assert.is_equal("1h00m",
|
||||
datetime.secondsToHClock(3600, true, true))
|
||||
assert.is_equal("1h00m",
|
||||
datetime.secondsToHClock(3599, true, true))
|
||||
assert.is_equal("59m",
|
||||
datetime.secondsToHClock(3569, true, true))
|
||||
assert.is_equal("10h01m",
|
||||
datetime.secondsToHClock(36060, true, true))
|
||||
end)
|
||||
it("should convert seconds to 0h00'00'' format", function()
|
||||
assert.is_equal("0\"",
|
||||
datetime.secondsToHClock(0))
|
||||
assert.is_equal("1'00\"",
|
||||
datetime.secondsToHClock(60))
|
||||
assert.is_equal("1'29\"",
|
||||
datetime.secondsToHClock(89))
|
||||
assert.is_equal("1'30\"",
|
||||
datetime.secondsToHClock(90))
|
||||
assert.is_equal("1'50\"",
|
||||
datetime.secondsToHClock(110))
|
||||
assert.is_equal("2'00\"",
|
||||
datetime.secondsToHClock(120))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("secondsToClockDuration()", function()
|
||||
it("should change type based on format", function()
|
||||
assert.is_equal("10h01m30s",
|
||||
datetime.secondsToClockDuration("modern", 36090, false, true))
|
||||
assert.is_equal("10:01:30",
|
||||
datetime.secondsToClockDuration("classic", 36090, false))
|
||||
assert.is_equal("10:01:30",
|
||||
datetime.secondsToClockDuration("unknown", 36090, false))
|
||||
assert.is_equal("10:01:30",
|
||||
datetime.secondsToClockDuration(nil, 36090, false))
|
||||
end)
|
||||
it("should pass along withoutSeconds", function()
|
||||
assert.is_equal("10h01m30s",
|
||||
datetime.secondsToClockDuration("modern", 36090, false, true))
|
||||
assert.is_equal("10h02m",
|
||||
datetime.secondsToClockDuration("modern", 36090, true, true))
|
||||
assert.is_equal("10:01:30",
|
||||
datetime.secondsToClockDuration("classic", 36090, false))
|
||||
assert.is_equal("10:02",
|
||||
datetime.secondsToClockDuration("classic", 36090, true))
|
||||
end)
|
||||
it("should pass along hmsFormat for modern format", function()
|
||||
assert.is_equal("10h01'30\"",
|
||||
datetime.secondsToClockDuration("modern", 36090))
|
||||
assert.is_equal("10h01m30s",
|
||||
datetime.secondsToClockDuration("modern", 36090, false, true))
|
||||
assert.is_equal("10h02m",
|
||||
datetime.secondsToClockDuration("modern", 36090, true, true))
|
||||
assert.is_equal("10h02'",
|
||||
datetime.secondsToClockDuration("modern", 36090, true, false))
|
||||
assert.is_equal("10:01:30",
|
||||
datetime.secondsToClockDuration("classic", 36090, false, true))
|
||||
assert.is_equal("10:01:30",
|
||||
datetime.secondsToClockDuration("classic", 36090, false, false))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("secondsToDate()", function()
|
||||
it("should deliver a date string", function()
|
||||
local time = { year=2022, month=12, day=6, hour=13, min=30, sec=35 }
|
||||
local time_s = os.time(time)
|
||||
|
||||
assert.is_equal("2022-12-06",
|
||||
datetime.secondsToDate(time_s))
|
||||
assert.is_equal("2022-12-07",
|
||||
datetime.secondsToDate(time_s + 86400)) -- one day later
|
||||
assert.is_equal("Tue Dec 06 2022",
|
||||
datetime.secondsToDate(time_s, true))
|
||||
assert.is_equal("Wed Dec 07 2022",
|
||||
datetime.secondsToDate(time_s + 86400, true))
|
||||
end)
|
||||
end)
|
||||
describe("secondsToDateTime()", function()
|
||||
it("should should deliver a date and time string", function()
|
||||
local time = { year=2022, month=11, day=20, hour=9, min=57, sec=39 }
|
||||
local time_s = os.time(time)
|
||||
|
||||
assert.is_equal("2022-11-20 9:57",
|
||||
datetime.secondsToDateTime(time_s))
|
||||
assert.is_equal("2022-11-21 9:57",
|
||||
datetime.secondsToDateTime(time_s + 86400))
|
||||
|
||||
assert.is_equal("2022-11-20 9:57 AM",
|
||||
datetime.secondsToDateTime(time_s, true))
|
||||
assert.is_equal("2022-11-21 9:57 AM",
|
||||
datetime.secondsToDateTime(time_s + 86400, true))
|
||||
|
||||
assert.is_equal("Sun Nov 20 2022 9:57",
|
||||
datetime.secondsToDateTime(time_s, false, true))
|
||||
assert.is_equal("Mon Nov 21 2022 9:57",
|
||||
datetime.secondsToDateTime(time_s + 86400, false, true))
|
||||
|
||||
assert.is_equal("Sun Nov 20 2022 9:57 AM",
|
||||
datetime.secondsToDateTime(time_s, true, true))
|
||||
assert.is_equal("Mon Nov 21 2022 9:57 AM",
|
||||
datetime.secondsToDateTime(time_s + 86400, true, true))
|
||||
end)
|
||||
end)
|
||||
end)
|
Loading…
Reference in New Issue