This fragment is about to be reported (you'll remain on this page):
You can enter a comment to clarify the mistake if you would like to:
Standalone multilanguage PHP date and time formatting module - the ultimate solution with Unicode (UTF-8) support.
It can be used in many cases – from simple «Posted on 12.1.11» which can be handled by
date() or strftime() to complex «Posted 2 hours ago».
And there's even more – for example, you can tell the class to apply «ago-format» to
recent dates and use longer format for older records (via IF).
This also applies to future dates.
And, as mentioned above, everything is 100%-localizable.
Download DateFmt from GitHub.
As with standard date() and others date format is specified using a string. However, unlike standard functions DateFmt aims to be easy to remember and read (just like our friend project UverseWiki) so it uses different format strings.
Format string (we'll also call it just «format» sometimes) can also contain special constructions: AGO, AT, AGO-IF and their combinations. Such constructions are written in square brackets, e.g.: AGO[t]IF-FAR[d#my] and have case-sensitive names (ago[s] is a normal string while AGO[s] is a special construct).
The following sample format strings demonstrate how DateFmt is used in some common cases. Your can play with format strings at i-Tools.org.
|Now is D__, AT[d# of M__ y##] (h##ms).||now||Now is Thursday, 15 of July 2011 (08:01:55 PM).|
|(ru) Сегодня D__, AT[d# M__ y##] (h##ms).||now||Сегодня Пятница, 15 Июля 2011 (20:01:55).|
|Last commit was AGO[d.h].||-d*40 - h*14 - 12||Last commit was 40 days 14 hours ago.|
|(ru) Last commit was AGO[d.h].||-d*40 - h*14 - 12||Last commit was 40 дней 14 часов назад.|
|It was on H#:m## A.M..||now||It was on 20:01 PM.|
|(ru) Это было в H#:m## A.M..||now||Это было в 20:01 вечера.|
|This entry was posted AGO[*]AT D__, d# M__ y##.||-m*50 - 3||This entry was posted 50 minutes ago at Thursday, 15 July 2011.|
|This document was created on d#my.||@2011-01-12||This document was created on 12/01/2011.|
|(ru) This document was created on d#my.||@2011-01-12||This document was created on 12.01.2011.|
|A diary post saying //AT[d# m__ y##]// in its top right corner.||@2012-12-12||A diary post saying 12 december 2012 in its top right corner.|
|(ru) Записка с надписью //AT[d# m__ y##]// в правом верхнем углу.||@2012-12-12||Записка с надписью 12 декабря 2012 в правом верхнем углу.|
|(ru) Записка с надписью //d# m__ y##// в правом верхнем углу.||@2012-12-12||Записка с надписью 12 декабрь 2012 в правом верхнем углу.|
|This reply was posted AGO[s-d]IF-FAR[on d#my]AT D__.||+h*3||This reply was posted after 3 hours at Thursday.|
|This reply was posted AGO[s-d]IF-FAR[on d#my]AT D__.||+d*40||This reply was posted on 24/08/2011 at Wednesday.|
|Posted at d##-M_-y# h##m (AGO[h-y]_ since last post...||+h*0.5||Posted at 15-Jul-11 08:31 PM (half an hour since last post…|
|...and AGO[*]_ before next reply).||-m*5||…and 5 minutes before next reply).|
|(ru) Добавлено d##-M_-y# h##m (через AGO[h-y]_ после предыдущего сообщения...||+h*0.5||Добавлено 15-Июл-11 20:31 (через полчаса после предыдущего сообщения…|
|(ru) ...и за AGO[*]_ перед следующим).||-m*5||…и за 5 минут перед следующим).|
Syntax described here is used to format strings like «1 November 2010» or «5.03.2010» and is similar to PHP's date() function. However, format strings are different.
Generic format for a format string is a character followed by 1 or 2 hashes (#). Character represents a date/time element to output (days, hours, etc.) while hashes – their size or padding. For example, d# outputs current day without leading zero; d## outputs the same date but if it's 1-9 it'll be padded with one zero.
You can insert AM/PM (Ante meridiem and Post meridiem) markers using lower-case a.m. or upper-case A.M.. Note that AM/PM notation isn't used in some countries, though, so if you can use shortcut formats like h##m (see below) – they'll use language data for this.
Note: languages that don't normally use AM/PM might have different names for them that have similar meaning – for example, if you want to say something like «2:00 in the evening» write it as h#:m## A.M. and it'll be shown as «6:51 PM» in English and «6:51 вечера» in Russian (which is translated exactly as «in the evening»).
There are also 4 shortcut formats for full date and time that will respect
national formats (e.g. 1/24/2010 for Americans and 24.01.2010 for Russians) along
with usage of 12- or 24-hour time format.
These shortcut formats are:
You can also format date & time into names: 01 → January or Jan and so on.
There are 2 forms for most names – full (January) and short (Jan).
Name formats are defined similar to other date & time formatting – a character followed by 1 or 2 underscores (_).
Both m_ (M_) and m__ (M__) have aliases «mo_» (Mo_, mo__, Mo__) – if you would like to follow the style of the mo# date format.
The following 2 might look unexpected yet they correspond to our 100%-localizable paradigm (unlike the above they're case-insensitive):
This comes in handy when you need to reflect the distance between 2 dates. For example,
on forums a post can be added «after 5 hours» since the last post. Or a to-do item's
deadline can expire «in 12 days».
Thus we see that it's possible to format both past & future periods.
To turn this mode on you need to use AGO construct, for example: AGO[t]. AGO can also be combined with AT which is described here.
Note that there's also exact AGO mode which sligtly differs from the one described here.
This mode works as follows: you specify a set of periods (1 period = 1 character) that the given distance will be checked against; if at least one specified range could represent it without fraction (e.g. 5 minutes can be represented both as 300 seconds and 0.08 hours) then it's used, otherwise nearest smallest specified period is taken and the distance is then displayed as a fraction with 1 trailing digit (%1.1f format).
If more distances fall in between several ranges the result is rounded to the smallest
range (e.g. AGO[smh] results in 5 minutes rather than 0 hours).
For this reason you don't have to specify consecutive ranges (e.g. sec, min, hour) – if you need you can skip some of them (e.g. sec, hour) and even if a distance falls perfectly into the skipped range (e.g. 30 minutes) the nearest specified range will be used (here it'll be 1800 seconds).
Sometimes you'd like to display an exact interval in which something has taken place –
«2 days 45 minutes 3 seconds ago», or its shortened form: «2d 45min 3s ago».
You can do it using this construct: AGO[d.i.s].
Ranges are the same as for AGO – they're also case-insensitive; the order in which they're defined matters: AGO[y.h.w] outputs «1 year 2 hours 4 weeks ago» while AGO[w.y.h] outputs «4 weeks 1 year 2 hours ago».
As mentioned in section on AGO, fractions are never output unless there are
no ranges that could
contain given distance (AGO[h] against 5 minutes is 0.08 hours – minute
range «m» wasn't specified).
But if there's at least one matching range it'll be used – no matter how large number shows up: AGO[sm] against 10 days will yield 14400 minutes.
However, for more natural feeling this fraction rule has 2 exceptions (both work only if short mode (AGO-SHORT) wasn't enabled.
First exception is for numbers between 1.45-1.99 – they will be output as a string
(if language supports it) – in English this would sound rather long («one minute and a half»)
thus it's not used but in some others (e.g. Russian) it'll be displayed as «полторы минуты»
instead of «1 минута».
This also gives more accurate feeling of periods while avoiding fractions – «1» can refer to 1, 1.2, 1.6, etc. while «one and a half» means at least 1.5 and above – twice as accurate than just «1».
If there are any duplicated ranges they are discarded; if there are wrong characters that don't correspond to any range an exception is thrown. An error will also occur if you're using - but its start or end range was wrong.
By default AGO will say «…ago» or «after...». However, in some situations
you'd like to place your own word (thus binding format string to your language but
perhaps making it sound more natural).
This can be achieved by putting an underscore (_) after AGO or its IF. Compare the following examples:
Sometimes you'd like to make AGO output result in more concise form.
For example, AGO-SHORT[d] against 2 days will output «2d ago» instead of
«2 days ago».
The same goes for the b range albeit English words for «today» and «yesterday» («tomorrow») are already short enough and are not shortened further – but «day after today» becomes just «day after».
How short the words will be is language-dependent – but generally in languages using letters (i.e. not Japanese, etc.) a short word is adviced to have 7-8 characters and not more than 10 characters – contractions many be used when necessary.
This construction can only be used along with AGO and has 2 forms:
What kind of format does IF accept? It's the same date & time formatting described above.
In every case IF can be either IF-FAR or IF>n; also in every case AT can be appended: ...IF[...]AT.
This construct (actually they're two) is meant exactly for situations like «2 days ago at 0:01». However, ithis phrase is only natural to English – but no matter how localizable a project is if it says here and there something like «два дня назад at 0:01» it has chances to look worse than it would with no localization at all.
It's a bit hard to explain the concept based on English grammar rules (maybe because I'm
not a native English speaker or a linguist) but it makes sense in many other languages
so I'll try to.
Basically, there are 2 kind of dates:
DateFmt tries to be as much localizable as possible, including conforming to national standards when possible. Localization is carried out by DateFmt::$languages property which is assigned default language strings at the end of date.php file.
There are many languages which change word ending based on number it's connected to. In English this is just an addition of «s» to numbers other than 1: 0 week, 1 week, 2 week, etc. Other languages have more complicated rules, e.g. in Russian there are 4 different word endings for counters – additionally they are repeated after each 100th number.
PHPtrueor for any number above 4 otherwise.
PHP"AGO y"(i.e. s, m, h, d, s, w, y).
PHP"AGO short s"–
PHP"AGO short y".
PHP"AGO short s-at"–
PHP"AGO short y-at".
See also AT-form translation below.
An attempt to describe what AT-form was made in this secontion. For English this form is unused.
PHP"AGO y-at"(i.e. s, m, h, d, s, w, y).
PHP"AGO short s-at"–
PHP"AGO short y-at".
As described in Natural fractions sometimes DateFmt might want to output some fractions as words instead. This includes the following strings:
PHP"half y"(i.e. s, m, h, d, s, w, y) – for fractions like 0.5 sec, 0.9 years.
PHP"1.5 y"– for fractions like 1.5 days, 1.9 min.
Make sure that internal encoding of mbstring is set to UTF-8 before calling FormatAs(). This is not necessary for static Format() as it does so automatically (and restores original encoding before returning).
$your_timestamp = 158399691; // => Wednesday, 08/01/1975
$formatted = DateFmt::Format('D__, d##my', $your_timestamp, 'ru');
$date = new DateFmt( $entry['timestamp'] );
echo $date->FormatAs('Now: d#my h##m.'); // => Now: 08/01/1975 10:48.
// null $date equals to time(); null $language - to 'en'.
static function Format($str, $date = null, $language = null)
// the same but as an instance method.
// if array, will be used as is; if string it's considered an ISO-639-2 lang code (e.g. 'en').
// returns an array containing the result of running self tests defined in self::$selfTests.
static function RunSelfTests()
public $now = time()
static public $selfTests // an array of self-tests to be executed by RunSelfTests().
$date = new DateFmt;
$date->date = $lastPost['timestamp'];
$date->now = $currentPost['timestamp'];
echo $date->FormatAs('Posted AGO[*]_ before the last post in this thread.');
// => Posted 2 days before the last post in this thread.
Most of the time you wouldn't need to access them as they're used internally. However,
they are standalone and might prove useful in some cases.
Fields not listed here but present in the class definition are considered private and might be changed later.
PHParray('string name' => 'localized string', ...). This array is populated at the end of date.php.
PHPstatic FmtNumUsing($stem, $inflections, $numberRolls, $number)
PHPfloor((0.1 + 0.7) * 10)will result in 7.9999... float that upon removing fractional part will become by 1 less than it actually should be. This function will explicitly round up numbers that are ≥ 0.9999.
PHPFormatNormal($format, $wordForm = '')
PHPFormatDate($dayLeadZero = false, $withYear = true)
PHPFormatTime($hoursLeadZero = false, $withSeconds = false)
PHPFormatAGO($format, $options = array())
This exception is thrown when an attempt to load a language was made but there were
no localization for it (no entry in
PHPDateFmt::$languages). Base language that is
always present is
PHP$iso2 instance property containing two-character
ISO-639-2 language code (e.g. en, ru, jp).