“Time keeps on slippin’, slippin’, slippin’, into the future.” —Steve Miller Band / Fly Like An Eagle
Working with “time” in any programming language can be confusing to programmers. Furthermore, it’s not always obvious what the time and date functions mean and how they behave. This week’s tutorial discusses some of these issues and shows you how to work with things like date calculations, time zones, and date formatting.
Before we get to code, there are several important concepts to understand. To a computer, the basic unit of time is a second. From that one value, we can determine any date, or with fractions, even smaller measures of time. While one second is a short period of time to a human, it’s a very long period of time to a computer. Just as humans break down long periods of time into smaller blocks, the computer does as well. Besides seconds, some functions work in milliseconds (1/1000th of a second), sometimes written as uSec or uSeconds. Other functions work in microseconds (1/1,000,000th of a second or 1/1000th of a millisecond!). Advanced computers can work with even shorter periods of time, but for Corona, these are the three time measurements we’ll work with.
Microseconds and Milliseconds
system.getTimer()
This call returns the amount of time since your app started running and the value is returned in milliseconds (1/1000ths). Most devices will return a fractional time like:
1839.3949
The time is measured in microsecond resolution, but it’s converted to milliseconds for you and the fractional part is accurate to microseconds. “When will I ever need that resolution?“, you might be asking. Well, probably never. Perhaps if you’re working with GPS data or writing Einstein’s Pedometer (a clever app that calculates how much younger you are by how fast you move) you would need microsecond resolution, but for our apps, we’ll deal with frame rates of 30 or 60 frames per second (1/30th of a second to 1/60th of a second). Thus, having resolution more accurate than 1/1000th of a second isn’t important.
So this leaves us with the functions that run on milliseconds. Those include:
Since each of these accepts time in milliseconds, you would provide a value of 5000 to the function for an equivalent of 5 seconds. ½ second would be 500, 2½ seconds would be 2500, and so on.
timer.performWithDelay(500, someFunction) -- a half second delay
Simply multiply the time you want in seconds by 1000 to get the time for any of these functions.
Dealing With Longer Periods of Time
As stated above, the basic unit of time for a computer is one second. Both iOS and Android (as well as Mac OS X) are based on operating systems derived from Unix, and in those operating systems the standard “time” function returns the number of seconds since January 1, 1970 at midnight. Microsoft uses a different reference point, but since many apps are built using the language C, which had its origins in Unix, their library also uses this time reference point or Epoch.
Negative times are before 1970, positive times are after 1970. In Corona, to get the current number of seconds since Jan 1, 1970, you use the os.time() API call:
print( os.time() ) -- outputs something like: 1358015442
That number by itself is fairly meaningless — you can’t really determine the “real world” time that it represents — but it is very useful to calculate date math. Imagine that you’re doing a turn-based game and you want to know if the player needs a “nudge” because they’re taking too long to play. You would store the time of their last move:
player[1].lastMove = os.time()
Then, if you want to check if they’ve made a move in the last hour, you could write:
local now = os.time() if ( now > player[1].lastMove + 3600 ) then -- nudge the player end
“Where did the 3600 come from?“ Good question! That is 60 seconds * 60 minutes which is one hour.
You can also define some constant values like:
local DAY = 86400 -- ( 24 * 60 * 60 ) local HOUR = 3600 -- ( 60 * 60 ) local MINUTE = 60 local now = os.time() if ( now > player[1].lastMove + HOUR ) then -- nudge the player end
With time in seconds as an integer value, it becomes very easy to do date math.
- Is your time older than a week? Use 604800 ( 7 * 24 * 60 * 60 ).
- Set an alarm in 30 days? Use 2592000 ( 30 * 24 * 60 * 60 ).
Also, note that you don’t need to worry about the number of days in a month, leap years, or any other calendar oddities when working with time in pure seconds.
Working With Dates
Dates are a bit more challenging because they are strings which can be formatted in seemingly infinite ways including:
- April 1, 2010 4:53pm
- April 1, 2010 4:53 P.M.
- 4/1/10 16:53 MT
- Sun Jan 13 15:17:32 EST 2013
- 13-JAN-13 15:17
As a developer, you need to parse the dates into their component values — that is, you must get each part of the date/time string: month, day, year, hour, minute, seconds and time zone. If you have the months as strings, you can use a lookup table to convert the string to a number and then create a timestamp.
local monthNumbers = {}
monthNumbers["January"] = 1
monthNumbers["February"] = 2
monthNumbers["March"] = 3
--etc...
monthAsInteger = monthNumbers[monthString]
One very common date format that we see is the ISO-8601 time format, a string that looks like this:
2012-01-12T12:04:35.03-0400
To parse this date, we are going to use the string function string:match to break down the various bits into their individual values using patterns:
local pattern = "(%d+)%-(%d+)%-(%d+)%a(%d+)%:(%d+)%:([%d%.]+)([Z%p])(%d%d)%:?(%d%d)" local year, month, day, hour, minute, seconds, tzoffset, offsethour, offsetmin = dateString:match( pattern )
To new Lua programmers, this may look scary and complex because it is heavy on syntax. Let’s break it down to understand it better:
2012-01-12 T 12:04:35.03 -0400
The part before the T is a date in a very predictable and easy-to-separate format: year, month, and day as positive integers separated by hyphens. The letter T indicates that the time part is starting; afterward there’s another series of positive integers to determine hour, minutes and seconds separated by colons. Note that the seconds value can be a floating point number. Finally, the string may have an optional time zone string indicating the zone the time originated in. We’ll talk more about the time zone format in a bit.
The string:match() function lets you look for patterns, which use “wildcard” type searching across the span of a string. In this case, we are seeking numbers which can be found using the %d+ pattern. This tells Lua to search for any series of integers, 0 to 9, with the + indicating that we want to match one or more integers in a series, for example, 2102.
Since we want to capture these values as variables that have sensible names, we put the pattern sets inside parentheses. This tells Lua that we have one for each piece of the date/time that we want to retrieve. The date portion has hyphens that separate the components — these you simply write as the actual character, since you don’t need to actually capture them as part of your variables.
(%d+)%-(%d+)%-(%d+) -- gets the year, month and day %a -- handles the single letter T (%d+)%:(%d+)%:([%d%.]+) -- gets the hour, minutes and seconds
Note that in the last set, we are searching for both numbers and a possible dot (.), in case the seconds are formatted as a floating point value. Since a dot is a “magic character” in string matching, you must also place a percent sign (%) in front of the dot, telling Lua to look for the actual dot character.
Finally a + or - separates the timezone information, or a single Z indicates UTC. The string:match function will return one value for each of the patterns matched, and we store those values into our variables. To make it challenging, the ISO-8601 standard permits the timezone time to either be: +/-HHMM or +/-HH:MM. If there is no timezone information, the time is assumed to be local to your timezone. The pattern below, while somewhat complex, will handle the timezone information.
([Z%p])(%d%d)%:?(%d%d)
This pattern looks for a Z or punctuation characters like + or -, as represented by %p. This must be followed by two digits, an optional colon, and two more digits. Collectively, this pattern returns tzoffset, offsethour and offsetmin.
To convert this to a number that we can use for date math, we turn to the os.time() function (documentation), which can accept various parameters — one of which is a table of values representing each of the date components.
local timestamp = os.time(
{year=year, month=month, day=day, hour=hour, min=minute, sec=seconds} )
We now have a value that is in seconds since Jan 1, 1970 and we can use it in date math — except for one problem: we haven’t adjusted for time zones yet! Fortunately, it’s easy to fix since we now have the base time in seconds. No time zone information (tzoffset == nil) indicates the time is in local time. A character Z indicates that the time is in Coordinated Universal Time or UTC. If it’s in UTC or local time, we don’t need to make any time zone adjustments.
local offset = 0
if ( tzoffset ) then
if ( tzoffset == "+" or tzoffset == "-" ) then -- we have a timezone!
offset = offsethour * 60 + offsetmin
if ( tzoffset == "-" ) then
offset = offset * -1
end
timestamp = timestamp + offset
end
end
It’s important to factor time zones in because, by default, os.time() returns its value local to the timezone you are in. You can easily compare “now” to a timestamp and determine if something is in the past or future, or if a given amount of time has elapsed.
Here is the complete function that you can tuck into your library of code:
function makeTimeStamp(dateString)
local pattern = "(%d+)%-(%d+)%-(%d+)%a(%d+)%:(%d+)%:([%d%.]+)([Z%p])(%d%d)%:?(%d%d)"
local year, month, day, hour, minute, seconds, tzoffset, offsethour, offsetmin =
dateString:match(pattern)
local timestamp = os.time(
{year=year, month=month, day=day, hour=hour, min=minute, sec=seconds} )
local offset = 0
if ( tzoffset ) then
if ( tzoffset == "+" or tzoffset == "-" ) then -- we have a timezone!
offset = offsethour * 60 + offsetmin
if ( tzoffset == "-" ) then
offset = offset * -1
end
timestamp = timestamp + offset
end
end
return timestamp
end
Final note: this does not compensate for Daylight Savings Time, a topic that’s beyond the scope of this tutorial.
Converting a Timestamp to Something Readable
At some point, you will probably need to go the “other way” with your time and convert it to a date string in a format that your users can understand. This is the purpose of the os.date() function (documentation).
The os.date() function takes two optional parameters. Calling it with no parameters will return the current date/time for the time zone you’re in (or the zone your device thinks you’re in). This will be in a format like:
Sat Jan 12 14:07:30 2013
Fortunately, you can specify various formatting parameters to build dates as you like. These format parameters are based on the Unix/C++ library function strftime, and with that you can format the date/time in many different ways.
print( os.date("%A") ) -- prints out a string representing the weekday.
print( os.date("%A %l:%M%p") ) -- prints out "Saturday 2:30PM"
By default, the values returned by os.date() will be based on your locale or time zone. This is important if you want to create the type of date we used above, which is common in RSS feeds, Corona Cloud dates, and more. Thus, you need to output the time based on UTC by placing a single “!” in front of the format parameter:
print( os.date("%FT%X%z") ) -- outputs: 2013-01-12T15:30:09-0500
or if you wanted it in UTC:
print( os.date("!%FT%XZ") ) -- outputs: 2013-01-12T19:30:09Z
The various formatting parameters from strftime lets you get the month, day, year, weekday, am/pm in various formats, hour, minutes, etc. In summary, it allows you to format the date string in any format you like!
Counting Down Time
A common question is “How do I create a countdown timer?”
Like most things, the best practice depends on exactly what you’re trying to accomplish. If you simply want to end your level when 60 seconds have elapsed, you can use timer.performWithDelay() to fire an event after 60 seconds. If you need a more comprehensive solution with a visual UI output, you can either use a Runtime listener on the enterFrame event that lets you check the time, or use a repeating 1-second timer which can be easily paused and resumed if necessary.
-------- RUNTIME METHOD --------
local startTime = os.time()
local levelTime = 60
local displayTime = display.newText(levelTime, 0, 0, "Helvetica", 20)
local function checkTime(event)
local now = os.time()
displayTime.text = levelTime - (now - startTime)
if ( now > startTime + levelTime ) then
-- code to end the level
end
end
Runtime:addEventListener("enterFrame", checkTime)
-------- REPEATING TIMER METHOD --------
local levelTime = 60
local displayTime = display.newText( levelTime, 0, 0, "Helvetica", 20 )
local function checkTime(event)
levelTime = levelTime - 1
displayTime.text = levelTime
if ( levelTime <= 0 ) then
-- code to end the level
end
end
timer.performWithDelay( 1000, checkTime, levelTime )
Note that both of these examples also feature a text “counter” which updates for each second elapsed.
In Summary
Dealing with time and dates in Corona can be heavy on syntax, but hopefully this tutorial has exhibited that it’s not an insurmountable task! With a little effort and the convenient functions/methods above, you can “wield time” in ways that you might not have considered in the past.




Chevol
Thanks for the explanation Rob, this was very helpful!
Perry
strftime makes formatting time incredibly simple! You don’t want to know what I originally went through to format the UTC time how I wanted
Thanks for the tutorial and insights.
Perry
David Oshiro
Very useful and informative article! Thanks!
AD
What a great tutorial. Helped me understand string handling as well. Thank you Corona!
Andrew Thompson
Great tutorial but please consider a follow up that does deal with daylight savings time. I have an app coded in javascript which subtracts dates and time. Compensating for daylight savings time caused me a few headaches. My solution wasn’t elegant but sort of worked. I’d love to know how to tackle this issue properly using Lua.
Christopher Bradley
Thanks for writing such a thorough guide on os.time and os.date!
Jen Looper
Extremely helpful, Rob, thanks!
Mike Yacullo
Good article, thanks.
Just wanted to note that the makeTimeStamp function does not work for UTC dates.
Changing the pattern to:
local pattern = “(%d+)%-(%d+)%-(%d+)%a(%d+)%:(%d+)%:([%d%.]+)([Z%p])(%d?%d?)%:?(%d?%d?)”
will work for both UTC and local dates, although it will also let through badly-formatted local dates. If anyone can improve on this please post, regex is not my strong point!
Jack01
os.date in Corona does not seem to take in account the language settings of the device..
http://linux.die.net/man/3/strftime
When using for example %A %B the weekday and the month get displayed in English, regardless of the language used on the phone.
Jyrki
Yes, I noticed this, too. Looks like a bug in Corona to me, and I need to provide my own translations for weekdays.
Dave Baxter
What format do I send the date/time to the function makeTimeStamp ?
Cheers,
Dave