So now you understand the basics of os.time() and os.date() in Lua, right?
For the quickest of reviews, just remember that:
os.time() requires a {table} of units like year, month, day, hour, minute, second and returns a unix EPOC timestamp number.
os.date() requires a unix EPOC timestamp number, and returns
either a formatted string, or a {table} of units like year, month, day, hour, minute, second.
Let's look at a couple of skins and .lua code that demonstrates how this all works. They are not "practical" skins really, but by understanding what it going on with them, you can really wrap your head around these concepts.
Most of the explanation for what is being done and why are included in -- comments in the .lua code. Load the skin(s) and then open the .lua code for that skin to follow along. Feel free to reference back to the first post in this thread if you need to refresh yourself on exactly how the functions work in Lua.
Download the .rmskin
LuaDateTime_1.0.rmskin
LuaDateParse.ini / LuaDateParse.lua
This skin uses
Inline Lua section variables to call some functions in the Lua code that will "parse" a formatted date/time string into the separate elements, and turn them into UTC timestamps.
This is pretty much always going to be the first step. You are going to get strings like
2017-12-17T06:07:36Z or
2017-12-31T18:02:29-05:00 or
Monday, June 15, 2009 8:45:30 PM in your skins, from WebParser or RunCommand or FileView or QuotePlugin or other sources, and before you can use them to do anything, they need to be a timestamp.
This skin just shows a variety of date/time formatted string you might get, and how to parse them and properly turn them into timestamps. Then we turn them back into pretty formatted strings and display them.
LuaDateParse.ini:
Code: Select all
[Rainmeter]
Update=1000
DynamicWindowSize=1
AccurateText=1
[Lua]
Measure=Script
ScriptFile=LuaDateParse.lua
UpdateDivider=-1
[MeterFormat1]
Meter=String
W=310
FontSize=11
FontWeight=400
FontColor=255,255,255,255
SolidColor=47,47,47,255
Padding=5,5,5,5
AntiAlias=1
Text=Sun Jan 7 09:42:54 2018#CRLF#[&Lua:Format1('Sun Jan 7 09:42:54 2018')]
DynamicVariables=1
[MeterFormat2]
Meter=String
Y=5R
W=310
FontSize=11
FontWeight=400
FontColor=255,255,255,255
SolidColor=47,47,47,255
Padding=5,5,5,5
AntiAlias=1
Text=Sun Jan 7 09:42:54 2018#CRLF#[&Lua:Format2('Sun Jan 7 09:42:54 2018')]
DynamicVariables=1
[MeterFormat3]
Meter=String
Y=5R
W=310
FontSize=11
FontWeight=400
FontColor=255,255,255,255
SolidColor=47,47,47,255
Padding=5,5,5,5
AntiAlias=1
Text=2017-12-31T18:02:29-05:00#CRLF#[&Lua:Format3('2017-12-31T18:02:29-05:00')]
DynamicVariables=1
[MeterFormat4]
Meter=String
Y=5R
W=310
FontSize=11
FontWeight=400
FontColor=255,255,255,255
SolidColor=47,47,47,255
Padding=5,5,5,5
AntiAlias=1
Text=Monday, June 15, 2009 8:45:30 PM#CRLF#[&Lua:Format4('Monday, June 15, 2009 8:45:30 PM')]
DynamicVariables=1
LuaDateParse.lua:
Code: Select all
function Initialize()
monthShortTable={Jan=1,Feb=2,Mar=3,Apr=4,May=5,Jun=6,Jul=7,Aug=8,Sep=9,Oct=10,Nov=11,Dec=12}
monthFullTable={January=1,February=2,March=3,April=4,May=5,June=6,July=7,August=8,September=9,October=10,November=11,December=12}
-- All timestamps in Lua are in Unix EPOCH format, which is defined as:
-- The number of seconds since 00:00:00 Coordinated Universal Time (UTC), Thursday, 1 January 1970,
-- minus the number of leap seconds that have taken place since then.
-- The full list of date / time format codes for os.date() are available here:
-- http://www.cplusplus.com/reference/ctime/strftime/
-- Information about Lua's Pattern Matching (similar, but NOT the same as PCRE regular expression) is here:
-- http://lua-users.org/wiki/PatternsTutorial
-- Note that when we create a timestamp with os.time() and it is a UTC time, we set it with isdst=false in
-- the input table to os.time(). UTC never follows or considers Daylight Saving Time. If we don't set a value
-- for isdst in our os.time() table, it will use the current value for that on your local system. That is not going
-- to be correct 1/2 of the year when creating or comparing UTC date / time values.
end
function Format1(timeString)
-- timeString = 'Sun Jan 7 09:42:54 2018'
-- This has no definition of time zone, Let's assume that it is in UTC.
-- Note that the os.time() function requires a table that is all numbers, so we
-- need to use the monthShortTable table defined in Initialize() to turn the
-- "Jan" into 1.
formatPattern = '^%a+%s+(%a+)%s+(%d+)%s+(%d+):(%d+):(%d+)%s+(%d+)$'
monthText, day, hour, min, sec, year = timeString:match(formatPattern)
month=monthShortTable[monthText]
timeStamp = os.time({month=month, day=day, year=year, hour=hour, min=min, sec=sec, isdst=false})
formatedString = os.date('%A, %B %d, %Y at %I:%M:%S %p', timeStamp)
-- Just for cosmetics, strip off any leading zeros on the day, and on the hour.
-- Regrettably, Lua's os.date() doesn't support Rainmeter's "#" character to suppress leading zeros.
formatedString = formatedString:gsub(' 0',' ')
return formatedString..'\nUnix timestamp is '..timeStamp
end
function Format2(timeString)
-- timeString = 'Sun Jan 7 09:42:54 2018'
-- This is the same string as our first example, but let's assume that it is in local time.
-- We leave off any isdst value, so our local Daylight Saving Time is considered.
formatPattern = '^%a+%s+(%a+)%s+(%d+)%s+(%d+):(%d+):(%d+)%s+(%d+)$'
monthText, day, hour, min, sec, year = timeString:match(formatPattern)
month=monthShortTable[monthText]
timeStamp = os.time({month=month, day=day, year=year, hour=hour, min=min, sec=sec})
-- Let's convert that local time to UTC time, which can help in doing date math comparisons elsewhere.
-- To convert to UTC time, we find the difference in seconds between the current local and utc times.
-- os.date('*t') returns the current local time and os.date('!*t') returns the current utc time.
-- That will be the "offset" in seconds. Simply subtract that from our timeStamp, and we
-- have converted that local time to UTC time.
offsetSeconds = os.time(os.date('*t')) - os.time(os.date('!*t'))
timeStamp = timeStamp - offsetSeconds
formatedString = os.date('%A, %B %d, %Y %I:%M:%S %p', timeStamp)
formatedString = formatedString:gsub(' 0',' ')
return formatedString..'\nUnix timestamp is '..timeStamp
end
function Format3(timeString)
-- timeString = '2017-12-31T18:02:29-05:00'
-- This has a defined timezone offset in +/- hours:minutes at the end.
-- We will subtract or add that offset in seconds to the time to get UTC time.
formatPattern = '^(%d+)-(%d+)-(%d+)T(%d+):(%d+):(%d+)(.)(%d+):(%d+)$'
year, month, day, hour, min, sec, offsetDirection, offsetHours, offsetMinutes = timeString:match(formatPattern)
timeStamp = os.time({month=month, day=day, year=year, hour=hour, min=min, sec=sec, isdst=false})
-- Convert the offsetHours and offsetMinutes into seconds.
-- Then based on the offsetDirection, either add or subtract those seconds to
-- get the UTC time to use in date math comparisons. If the timezone offset is
-- negative, you are "behind" UTC time and need to add the value. If the timezone
-- offset is positive, you are "ahead of" UTC time and need to subtract the value.
offsetTotal = (offsetHours + (offsetMinutes * 60)) * 3600
if offsetDirection == '-' then
timeStamp = timeStamp + offsetTotal
else
timeStamp = timeStamp - offsetTotal
end
formatedString = os.date('%A, %B %d, %Y %I:%M:%S %p', timeStamp)
formatedString = formatedString:gsub(' 0',' ')
return formatedString..'\nUnix timestamp is '..timeStamp
end
function Format4(timeString)
-- timeString = 'Monday, June 15, 2009 8:45:30 PM'
-- There is no indication of time zone. Let's assume it's in local time.
-- Note that in general, any date you get that is in 12-hour format will be a local time. UTC
-- time is almost never expressed in 12-hour time.
-- This is in 12-hour time, with the trailing meridiem AM/PM indicator. We need to
-- convert this to 24-hour time to use it with os.time() and do proper date/time math with it.
-- In addition, note that the month is again text, but using the monthFullTable format. We
-- will look it up to change "June" to 6.
formatPattern = '^%a+,%s+(%a+)%s+(%d+),%s+(%d+)%s+(%d+):(%d+):(%d+)%s+(%a+)$'
monthText, day, year, hour, min, sec, meridiem = timeString:match(formatPattern)
month=monthFullTable[monthText]
if (string.upper(meridiem) == 'AM') and (tonumber(hour) == 12) then
hour = 0
elseif (string.upper(meridiem) == 'PM') and (tonumber(hour) < 12) then
hour = hour + 12
end
timeStamp = os.time({month=month, day=day, year=year, hour=hour, min=min, sec=sec})
-- As we did before, let's convert this to UTC time.
offsetSeconds = os.time(os.date('*t')) - os.time(os.date('!*t'))
timeStamp = timeStamp - offsetSeconds
formatedString = os.date('%A, %B %d, %Y %I:%M:%S %p', timeStamp)
formatedString = formatedString:gsub(' 0',' ')
return formatedString..'\nUnix timestamp is '..timeStamp
end
2.png
LuaDateCompare.ini / LuaDateCompare.lua
This skin uses
Inline Lua section variables to repeatedly use a function in the Lua code to "parse" formatted date/time strings into the separate elements, and turn them into UTC timestamps.
Note, the ability to re-use a single function multiple times in your skin is the power of Lua in a nutshell!
It will then compare these timestamps to the current UTC time, and display the resulting difference as "days, hours, minutes and seconds".
LuaDateCompare.ini:
Code: Select all
[Rainmeter]
Update=1000
DynamicWindowSize=1
AccurateText=1
[Lua]
Measure=Script
ScriptFile=LuaDateCompare.lua
UpdateDivider=-1
[MeterElapsed1]
Meter=String
W=360
FontSize=11
FontWeight=400
FontColor=255,255,255,255
SolidColor=47,47,47,255
Padding=5,5,5,5
AntiAlias=1
Text=[&Lua:ElapsedTime('2018-01-09T15:57:00Z')]
DynamicVariables=1
[MeterElapsed2]
Meter=String
Y=5R
W=360
FontSize=11
FontWeight=400
FontColor=255,255,255,255
SolidColor=47,47,47,255
Padding=5,5,5,5
AntiAlias=1
Text=[&Lua:ElapsedTime('2018-03-15T13:02:40Z')]
DynamicVariables=1
[MeterElapsed3]
Meter=String
Y=5R
W=360
FontSize=11
FontWeight=400
FontColor=255,255,255,255
SolidColor=47,47,47,255
Padding=5,5,5,5
AntiAlias=1
Text=[&Lua:ElapsedTime('2017-09-22T16:22:34Z')]
DynamicVariables=1
LuaDateCompare.lua:
Code: Select all
function ElapsedTime(timeString)
-- timeString = '2018-01-09T15:57:00Z'
-- First, let's get a timestamp for the input timeString. You can check the
-- LuaDateParse.lua file to see some other examples of parsing date / time strings.
-- Note that we set isdst=false, as we want to compare a fixed UTC time, which never
-- follows DST, to the current UTC time, which is also never in DST. If we set an
-- os.time() value to a table that doesn't include a value for isdst, the value for isdst
-- currently on your local system will be used.
formatPattern = '^(%d+)-(%d+)-(%d+)T(%d+):(%d+):(%d+)Z$'
year, month, day, hour, min, sec = timeString:match(formatPattern)
thenStamp = os.time({month=month, day=day, year=year, hour=hour, min=min, sec=sec, isdst=false})
-- Create a formatted date / time string followed by a linefeed, to put at the beginning.
preString = os.date('%A, %B %d, %Y at %I:%M:%S %p', thenStamp)..'\n'
-- Just for cosmetics, strip off any leading zeros on the day, and on the hour.
-- Regrettably, Lua's os.date() doesn't support Rainmeter's "#" character to suppress leading zeros.
preString = preString:gsub(' 0',' ')
-- Now let's find out how many elapsed seconds have passed or remain until the input date / time,
-- by subtracting that timestamp from the current UTC time.
nowStamp = os.time(os.date('!*t'))
elapsedSeconds = nowStamp - thenStamp
-- Note that if the date is in the future, the difference will be negative. Using math.abs() causes
-- the difference in seconds, which are the same in either case, to always be a positive number.
if thenStamp > nowStamp then postString = ' remaining' else postString = ' elapsed' end
elapsedSeconds = math.abs(elapsedSeconds)
-- Call the ConvertSeconds() function to turn those elapsed seconds into a table
-- of the days, hours, minutes and seconds. We will call that table "et", just to
-- keep the name short.
et = ConvertSeconds(elapsedSeconds)
-- You don't want to say "1 minutes", but rather "1 minute"
if et.days == 1 then daysText = ' day, ' else daysText = ' days, ' end
if et.hours == 1 then hoursText = ' hour, ' else hoursText = ' hours, ' end
if et.mins == 1 then minsText = ' minute, ' else minsText = ' minutes, ' end
if et.secs == 1 then secsText = ' second' else secsText = ' seconds' end
-- Create the final output string.
-- Start with the formatted date / time string created above, with the linefeed, followed by
-- The days, hours, minutes and seconds. Don't show more than you need to. If there are
-- no days, there is no need to have "0 days" at the beginning. Stick postString at the end.
if et.days > 0 then
outString = preString..et.days..daysText..et.hours..hoursText..et.mins..minsText..et.secs..secsText..postString
elseif et.hours > 0 then
outString = preString..et.hours..hoursText..et.mins..minsText..et.secs..secsText..postString
elseif et.mins > 0 then
outString = preString..et.mins..minsText..et.secs..secsText..postString
else
outString = preString..et.secs..secsText..postString
end
return outString
end
function ConvertSeconds(secondsArg)
local daysDiff = math.floor(secondsArg / 86400)
local remainder = (secondsArg % 86400)
local hoursDiff = math.floor(remainder / 3600)
local remainder = (secondsArg % 3600)
local minsDiff = math.floor(remainder / 60)
local secsDiff = (remainder % 60)
local elapsedTable = {days=daysDiff, hours=hoursDiff, mins=minsDiff, secs=secsDiff}
return elapsedTable
end
1.png
Hopes this helps with seeing how this date / time stuff can be used in a skin. In our next installment, we will look at at a more "practical" example that parses an RSS news feed, and displays how "old" each item is in the skin. Stay tuned...
You do not have the required permissions to view the files attached to this post.