You will encounter date/time functions all over PHP-Nuke: cookie expiration time, broadcast message duration time, article time, server time. PHP offers many functions that help us with date/time tasks. Here we will cover:
How to find the last date of some article, download etc. (Section 27.13.1),
How to adjust server time (Section 27.13.2),
How to use the Discordian Calendar (Section 27.13.3).
Dates are stored in many tables of PHP-Nuke. The date fields are used to hold the date we
entered a banner and whhen that banner expires,
wrote a private message,
wrote a comment,
entered a download link,
voted for something (article, download, link etc.),
wrote in our journal,
inserted a web link,
wrote a review,
wrote an article
and so on. Sometimes, while writing a modification (Chapter 17, Chapter 18), or even a new block (Chapter 20) or module (Chapter 21), you may want to compute the last date that an event like the above has happened.
Whenever you encounter a problem of this type - find the first, the last, the minimum, the maximum, the average...of some attribute that is stored in a table field of PHP-Nuke - think aggregate functions. There is no need to go through a loop of all records in our table, just to find an aggregate function of its fields - the database does it for us:
SELECT max( date ) FROM nuke_downloads_downloads; |
The above SQL query will compute the maximum date (i.e. the last date) of all links n the Downloads section. You can use SQL queries like the above in your blocks and modules. To start with, create (with a decent text editor, see Chapter 11) a file that contains (see How to compute the date of the last file added in Downloads):
<?php require_once("mainfile.php"); global $db; include("header.php"); $sql = "SELECT max(date) FROM nuke_downloads_downloads"; $result = $db->sql_query($sql); $row = $db->sql_fetchrow($result); $maxdate = $row["max(date)"]; echo "maxdate = $maxdate"; echo "<br><br>"; ?> |
name it query.php, upload it in the same dir on your server where also mainfile.php is located, then point your browser to query.php. You should see a line like
maxdate = 2003-11-17 15:33:03 |
This example illustrates the use of the new SQL layer (see Section 28.5) for the computation of aggregates. Note that the argument of the $row array is a string that mimics exactly the aggregate function in the SELECT statement:
$maxdate = $row["max(date)"]; |
Here, “max(date)” is just a string and has nothing to do with the max() function.
Of course, this example is more a “proof of concept”, rather than something you should use in production - on the contrary, you should delete it from server as soon as you don't need it, for security reasons. But it serves as a starting point for more ambitious projects. like the following modification to the Downloads module:
Problem: You have various categories and subcategories defined in the Downloads section. For each category, you want to compute the last date that you entered some download link, either in this category, or in any of its subcategories.
We have already seen how to find the lst date of the downloads in one category above. What makes the situation here somewhat more complicated is the fact that we have to do the same computation for every subcategory of a category - however we don't know all those subcategory IDs a priori, we have to search for them.
More specifically, the steps to be taken for every category ID $cid are:
Compute the maximum date for that $cid.
Search the nuke_downloads_categories for all category IDs, whose parent ID is $cid. Let's call such a category ID $subcid (meaning that it is a subcategory, since it has a parent, $cid).
Compute the latest date a download was inserted in that $subcid. Do this for all $subcid's you can find for that $cid.
Compute the maximum of all those maximum dates (including the maximum date for $cid). This will be the latest date a download link was inserted in either the category $cid, or any of its subcategories $subcid.
The code that implements the above algorithm is (see How to compute the date of the last file added in Downloads):
Figure 27-12. Downloads: Main categories with last download date.
Downloads: Main categories with last download date.
Use JOIN | |
---|---|
This algorithm would be a perfect example of an application of the SQL JOIN query, since the “parent-child” information is kept in a different table, nuke_downloads_categories, while the dates themselves are kept in nuke_downloads_downloads. However, JOINs may not be supported by your version of MySQL, so we took the rather primitive (and usually slower) approach of nested SELECT statements in the code above. If you only have a few categories, subcategories and downloads, JOINs may not be a big deal for you, but the situation changes if you have thousands of them (see Section 27.6 on how you can enter that many, without subjecting yourself to Repetitive Stress Injury ). You are encouraged to experiment with JOINs and test if they improve the algorithm's performance! |
Figure 27-13. Your Info profile: Timezone and Date settings.
Your Info profile: Timezone and Date settings.
In some cases, perhaps due a misconfiguration of your server, the time shown up in the News stories is wrong. Normally, it should be possible to control it by changing the time settings in either the Preferences of the administration panel, or the user preferences (Figure 27-13). However, there will be situations where a quick fix is desirable.
You can offset the time by tweaking the following line in the mainfile.php in the function formatTimeStamp (see How to adjust server time in PHP-Nuke):
$datetime = strftime(""._DATESTRING."", mktime($datetime[4],$datetime[5],$datetime[6], $datetime[2],$datetime[3],$datetime[1])); |
For example to add one hour (3600 seconds), you would simply add 3600:
$datetime = strftime(""._DATESTRING."", mktime($datetime[4],$datetime[5],$datetime[6], $datetime[2],$datetime[3],$datetime[1]) + 3600); |
or, to subtract two hours:
$datetime = strftime(""._DATESTRING."", mktime($datetime[4],$datetime[5],$datetime[6], $datetime[2],$datetime[3],$datetime[1]) - 7200); |
A situation that calls for server time modification arises when your server is located in a different timezone than your geographic location - say you are located in Japan, but your hosting company is in Canada (see How to adjust server time in PHP-Nuke). The article on phpbuilder.com On User-Defined Timezones in PHP explains the problem in detail. Quoting:
PHP's 'mktime' and 'date' functions work well as a pair without the help of any other timestamp manipulation routines, but only if the application in which they are used is concerned solely with display and entry of time in the servers timezone. If an application needs to handle entry from a timezone other than that in which the server is located something more than 'mktime' and 'date' is required.
Two things are required to accomplish this: a location independent format for storing time in the database, and methods to translate to and from that format into the user's local time.
Read the original article for a solution. To apply it to PHP-Nuke, you would need to replace the time functions with the new ones, read the time offset from the user's profile and display the result.
To reflect the new time zone that may be implied by the hardcoded time offset, you would also want to change the _DATESTRING definition in your language file, e.g. language/lang-english.php:
define("_DATESTRING","%A, %B %d @ %T %Z"); |
You can change the "%A, %B %d @ %T %Z" string to whatever you deem appropriate. PHP gives to some placeholders a special meaning:
%a - abbreviated weekday name according to the current locale
%A - full weekday name according to the current locale
%b - abbreviated month name according to the current locale
%B - full month name according to the current locale
%c - preferred date and time representation for the current locale
%C - century number (the year divided by 100 and truncated to an integer, range 00 to 99)
%d - day of the month as a decimal number (range 01 to 31)
%D - same as %m/%d/%y
%e - day of the month as a decimal number, a single digit is preceded by a space (range ' 1' to '31')
%g - like %G, but without the century.
%G - The 4-digit year corresponding to the ISO week number (see %V). This has the same format and value as %Y, except that if the ISO week number belongs to the previous or next year, that year is used instead.
%h - same as %b
%H - hour as a decimal number using a 24-hour clock (range 00 to 23)
%I - hour as a decimal number using a 12-hour clock (range 01 to 12)
%j - day of the year as a decimal number (range 001 to 366)
%m - month as a decimal number (range 01 to 12)
%M - minute as a decimal number
%n - newline character
%p - either `am' or `pm' according to the given time value, or the corresponding strings for the current locale
%r - time in a.m. and p.m. notation
%R - time in 24 hour notation
%S - second as a decimal number
%t - tab character
%T - current time, equal to %H:%M:%S
%u - weekday as a decimal number [1,7], with 1 representing Monday
%U - week number of the current year as a decimal number, starting with the first Sunday as the first day of the first week
%V - The ISO 8601:1988 week number of the current year as a decimal number, range 01 to 53, where week 1 is the first week that has at least 4 days in the current year, and with Monday as the first day of the week. (Use %G or %g for the year component that corresponds to the week number for the specified timestamp.)
%W - week number of the current year as a decimal number, starting with the first Monday as the first day of the first week
%w - day of the week as a decimal, Sunday being 0
%x - preferred date representation for the current locale without the time
%X - preferred time representation for the current locale without the date
%y - year as a decimal number without a century (range 00 to 99)
%Y - year as a decimal number including the century
%Z - time zone or name or abbreviation
%% - a literal `%' character
You can combine them with punctuation or extra words to construct the datetime string of your liking. You are not limited to the above interpretation. For example, you can just delete the %Z, and replace it with EST or whatever you time zone is (if you cannot set it from the preferences, that is). There are some points to bear in mind while experimenting:
Given a 'bare number' in seconds, e.g. 143567, strftime() will format this number according to your local timezone. Thus, for Germany, you will get an extra hour, since CET is GMT +1. If you are interested in simply formatting a number to give you hours, minutes and seconds (very quick for showing time values), then you should always use 'gmstrftime' instead, which will not adjust the time for your local timezone. You set the local timezone in the preferences section of the administration panel, or the preferences in the user info panel. Valid timezone codes can (usually) be seen (on Linux, at least) in /usr/share/zoneinfo or /usr/lib/zoneinfo (according to the tzset manpage).
The call to mktime
mktime($datetime[4],$datetime[5],$datetime[6],$datetime[2],$datetime[3],$datetime[1]) |
means something equivalent to
mktime($hour,$minute,$second,$month,$day,$year) |
assuming that $hour, $minute etc. are filled with the right values (this is exactly what the datetime array does for us).
See the PHP manual page for strftime for more information and examples about date string formatting.
Date and time functions in PHP are very flexible, but I still have to find a PHP function that converts my date strings to the Discordian Calendar:
The Discordian calendar divides the year into five seasons (commonly known as Chaos (la kalsa), Discord (la tolsarxe), Confusion (la cfipu), Bureaucracy (la lujycatni) and the Aftermath (la jalge) ) of 73 days each. Every four years, an extra day (St. Tib's Day / la tib noi vrude) is inserted between Chaos 59 and Chaos 60.
The Discordian calendar defines a week as having five days, named Sweetmorn(la titcer), Boomtime (la bumtem), Pungenday (la pynjdei/cpinydei), Prickle-Prickle (la kilkil) and Setting Orange (after the Five Elements). Chaos 1 is always Sweetmorn, Chaos 2 Boomtime, The Aftermath 73 Setting Orange(la canci narju), etc. St. Tib's Day is none of the five days of the week.
The Discordian calendar's year 0 (notionally the time of the Original Snub) is known in the Gregorian calendar as 1166 BC; alternatively, the Gregorian year 0 (notionally the birth of the Jewish mystic Yeshua ben Yosef) is known as 1166 YOLD in the Discordian calendar. The year known in the Gregorian calendar as 2001 is known as 3167 in the Discordian calendar.
Just in case you wanted to know.
How do we output a date string in the Discordian Calendar? If you want to write your own PHP ddate function, you might find the code of the Perl Date::Discordian module useful. If the settings in your php.ini allow the execution of system commands (either safe_mode is off, or safe_mode is on and safe_mode_exec_dir contains the directory that holds the ddate executable, see safe mode settings) and you are running off a Linux box, then you can use the output of ddate to construct a discordian date in mainfile.php:
$datetime = explode("Today is", exec('ddate')); |
This will result in something like
Setting Orange, the 41st day of Bureaucracy in the YOLD 3169 |
as the value of $datetime displayed in the News posts. More on this confusing topic in Principia Discordia, the Opiatum Magnum of the Discordian Philosophy, as well as in the Discordian Forum - where Enlightenment meets Confusion (TM)...