The standard libraries offer few functions to manipulate date and time in Lua. As usual, all it offers is what is available in the standard C libraries. Nevertheless, despite its apparent simplicity, we can make quite a lot with this basic support.
Lua uses two representations for date and time. The first one is through a single number, usually an integer. Although not required by ISO C, on most systems this number is the number of seconds since some fixed date, called the epoch. In particular, both in POSIX and Windows systems the epoch is Jan 01, 1970, 0:00 UTC.
The second representation that Lua uses for dates and times
is a table.
Such date tables have the following significant fields:
year
, month
, day
,
hour
, min
, sec
,
wday
, yday
,
and isdst
.
All fields except isdst
have integer values.
The first six fields have obvious meanings.
The wday
field is the day of the week (one is Sunday);
the yday
field is the day of the year (one is January 1st).
The isdst
field is a Boolean,
true if daylight saving is in effect.
As an example,
Sep 16, 1998, 23:48:10 (a Wednesday)
corresponds to the following table:
{year = 1998, month = 9, day = 16, yday = 259, wday = 4, hour = 23, min = 48, sec = 10, isdst = false}
Date tables do not encode a time zone. It is up to the program to interpret them correctly with respect to time zones.
The function os.time
,
when called without arguments,
returns the current date and time,
coded as a number:
> os.time() --> 1439653520
This date corresponds to Aug 15, 2015, 12:45:20.[13] In a POSIX system, we can use some basic arithmetic to decompose that number:
local date = 1439653520 local day2year = 365.242 -- days in a year local sec2hour = 60 * 60 -- seconds in an hour local sec2day = sec2hour * 24 -- seconds in a day local sec2year = sec2day * day2year -- seconds in a year -- year print(date // sec2year + 1970) --> 2015.0 -- hour (in UTC) print(date % sec2day // sec2hour) --> 15 -- minutes print(date % sec2hour // 60) --> 45 -- seconds print(date % 60) --> 20
We can also call os.time
with a date table,
to convert the table representation to a number.
The year
, month
, and day
fields are mandatory.
The hour
, min
, and sec
fields
default to noon (12:00:00) when not provided.
Other fields (including wday
and yday
)
are ignored.
> os.time({year=2015, month=8, day=15, hour=12, min=45, sec=20}) --> 1439653520 > os.time({year=1970, month=1, day=1, hour=0}) --> 10800 > os.time({year=1970, month=1, day=1, hour=0, sec=1}) --> 10801 > os.time({year=1970, month=1, day=1}) --> 54000
Note that 10800 is three hours (the time zone) in seconds and 54000 is 10800 plus 12 hours in seconds.
The function os.date
, despite its name,
is a kind of reverse of os.time
:
it converts a number representing the date and time
to some higher-level representation,
either a date table or a string.
Its first parameter is a format string,
describing the representation we want.
The second parameter is the numeric date–time;
it defaults to the current date and time if not provided.
To produce a date table,
we use the format string "*t"
.
For instance, the call os.date("*t", 906000490)
returns the following table:
{year = 1998, month = 9, day = 16, yday = 259, wday = 4, hour = 23, min = 48, sec = 10, isdst = false}
In general,
we have that os.time(os.date("*t", t)) == t
,
for any valid time t
.
Except for isdst
,
the resulting fields are integers in the following ranges:
| a full year |
| 1–12 |
| 1–31 |
| 0–23 |
| 0–59 |
| 0–60 |
| 1–7 |
| 1–366 |
(Seconds can go up to 60 to allow for leap seconds.)
For other format strings,
os.date
returns a copy of the string with specific directives
replaced by information about the given time and date.
A directive consists of a percent sign followed by a letter,
as in the next example:
print(os.date("a %A in %B")) --> a Tuesday in May print(os.date("%d/%m/%Y", 906000490)) --> 16/09/1998
When relevant,
representations follow the current locale.
For instance, in a locale for Brazil–Portuguese,
%A
would result in "terça-feira"
and %B
in "maio"
.
Figure 12.1, “Directives for function os.date
” shows the main directives.
For each directive, it presents its meaning
and its value for September 16, 1998 (a Wednesday),
at 23:48:10.
Figure 12.1. Directives for function os.date
| abbreviated weekday name (e.g., |
| full weekday name (e.g., |
| abbreviated month name (e.g., |
| full month name (e.g., |
| date and time (e.g., |
| day of the month ( |
| hour, using a 24-hour clock ( |
| hour, using a 12-hour clock ( |
| day of the year ( |
| month ( |
| minute ( |
| either |
| second ( |
| weekday ( |
| week of the year ( |
| date (e.g., |
| time (e.g., |
| two-digit year ( |
| full year ( |
| timezone (e.g., |
|
For numerical values, the table shows also their range of possible values. Here are some examples, showing how to create some ISO 8601 formats:
t = 906000490 -- ISO 8601 date print(os.date("%Y-%m-%d", t)) --> 1998-09-16 -- ISO 8601 combined date and time print(os.date("%Y-%m-%dT%H:%M:%S", t)) --> 1998-09-16T23:48:10 -- ISO 8601 ordinal date print(os.date("%Y-%j", t)) --> 1998-259
If the format string starts with an exclamation mark,
then os.date
interprets the time in UTC:
-- the Epoch print(os.date("!%c", 0)) --> Thu Jan 1 00:00:00 1970
If we call os.date
without any arguments,
it uses the %c
format, that is,
date and time information in a reasonable format.
Note that the representations for %x
,
%X
, and %c
change according to the locale and the system.
If you want a fixed representation,
such as dd/mm/yyyy
,
use an explicit format string, such as "%d/%m/%Y"
.
When os.date
creates a date table,
its fields are all in the proper ranges.
However,
when we give a date table to os.time
,
its fields do not need to be normalized.
This feature is an important tool to manipulate dates
and times.
As a simple example, suppose we want to know the date 40 days from now. We can compute that date as follows:
t = os.date("*t") -- get current time print(os.date("%Y/%m/%d", os.time(t))) --> 2015/08/18 t.day = t.day + 40 print(os.date("%Y/%m/%d", os.time(t))) --> 2015/09/27
If we convert the numeric time back to a table, we get a normalized version of that date–time:
t = os.date("*t") print(t.day, t.month) --> 26 2 t.day = t.day - 40 print(t.day, t.month) --> -14 2 t = os.date("*t", os.time(t)) print(t.day, t.month) --> 17 1
In this example, Feb -14 has been normalized to Jan 17, which is 40 days before Feb 26.
In most systems, we could also add or subtract 3456000 (40 days in seconds) to the numeric time. However, the C standard does not guarantee the correctness of this operation, because it does not require numeric times to denote seconds from some epoch. Moreover, if we want to add some months instead of days, the direct manipulation of seconds becomes problematic, as different months have different durations. The normalization method, on the other hand, has none of these problems:
t = os.date("*t") -- get current time print(os.date("%Y/%m/%d", os.time(t))) --> 2015/08/18 t.month = t.month + 6 -- six months from now print(os.date("%Y/%m/%d", os.time(t))) --> 2016/02/18
We have to be careful when manipulating dates. Normalization works in a somewhat obvious way, but it may have some non-obvious consequences. For instance, if we compute one month after March 31, that would give April 31, which is normalized to May 1 (one day after April 30). That sounds quite natural. However, if we take one month back from that result (May 1), we arrive on April 1, not the original March 31. Note that this mismatch is a consequence of the way our calendar works; it has nothing to do with Lua.
To compute the difference between two times,
there is the function os.difftime
.
It returns the difference, in seconds,
between two given numeric times.
For most systems,
this difference is exactly the result of
subtracting on time from the other.
Unlike the subtraction, however,
the behavior of os.difftime
is guaranteed in any system.
The next example computes the number of days passed between
the release of Lua 5.2 and Lua 5.3:
local t5_3 = os.time({year=2015, month=1, day=12}) local t5_2 = os.time({year=2011, month=12, day=16}) local d = os.difftime(t5_3, t5_2) print(d // (24 * 3600)) --> 1123.0
With difftime
,
we can express dates as number of seconds since any
arbitrary epoch:
> myepoch = os.time{year = 2000, month = 1, day = 1, hour = 0} > now = os.time{year = 2015, month = 11, day = 20} > os.difftime(now, myepoch) --> 501336000.0
Using normalization, it is easy to convert that number of seconds back to a legitimate numeric time: we create a table with the epoch and set its seconds as the number we want to convert, as in the next example.
> T = {year = 2000, month = 1, day = 1, hour = 0} > T.sec = 501336000 > os.date("%d/%m/%Y", os.time(T)) --> 20/11/2015
We can also use os.difftime
to compute the running time of a piece of code.
For this task, however,
it is better to use os.clock
.
The function os.clock
returns
the number of seconds of CPU time used by the program.
Its typical use is to benchmark a piece of code:
local x = os.clock() local s = 0 for i = 1, 100000 do s = s + i end print(string.format("elapsed time: %.2f\n", os.clock() - x))
Unlike os.time
,
os.clock
usually has sub-second precision,
so its result is a float.
The exact precision depends on the platform;
in POSIX systems,
it is typically one microsecond.
Exercise 12.1: Write a function that returns the date–time exactly one month after a given date–time. (Assume the numeric coding of date–time.)
Exercise 12.2: Write a function that returns the day of the week (coded as an integer, one is Sunday) of a given date.
Exercise 12.3: Write a function that takes a date–time (coded as a number) and returns the number of seconds passed since the beginning of its respective day.
Exercise 12.4: Write a function that takes a year and returns the day of its first Friday.
Exercise 12.5: Write a function that computes the number of complete days between two given dates.
Exercise 12.6: Write a function that computes the number of complete months between two given dates.
Exercise 12.7: Does adding one month and then one day to a given date give the same result as adding one day and then one month?
Exercise 12.8: Write a function that produces the system’s time zone.
Personal copy of Eric Taylor <jdslkgjf.iapgjflksfg@yandex.com>