Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F1426053
DateFormatter.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
9 KB
Referenced Files
None
Subscribers
None
DateFormatter.php
View Options
<?php
/**
* Date formatter
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Parser
*/
namespace
MediaWiki\Parser
;
use
MediaWiki\Html\Html
;
use
MediaWiki\Language\Language
;
use
MediaWiki\MediaWikiServices
;
/**
* Date formatter. Recognises dates and formats them according to a specified preference.
*
* This class was originally introduced to detect and transform dates in free text. It is now
* only used by the {{#dateformat}} parser function. This is a very rudimentary date formatter;
* Language::sprintfDate() has many more features and is the correct choice for most new code.
* The main advantage of this date formatter is that it is able to format incomplete dates with an
* unspecified year.
*
* @ingroup Parser
*/
class
DateFormatter
{
/** @var string[] Date format regexes indexed the class constants */
private
$regexes
;
/**
* @var int[][] Array of special rules. The first key is the preference ID
* (one of the class constants), the second key is the detected source
* format, and the value is the ID of the target format that will be used
* in that case.
*/
private
const
RULES
=
[
self
::
ALL
=>
[
self
::
MD
=>
self
::
MD
,
self
::
DM
=>
self
::
DM
,
],
self
::
NONE
=>
[
self
::
ISO
=>
self
::
ISO
,
],
self
::
MDY
=>
[
self
::
DM
=>
self
::
MD
,
],
self
::
DMY
=>
[
self
::
MD
=>
self
::
DM
,
],
];
/**
* @var array<string,int> Month numbers by lowercase name
*/
private
$xMonths
=
[];
/**
* @var array<int,string> Month names by number
*/
private
$monthNames
=
[];
/**
* @var int[] A map of descriptive preference text to internal format ID
*/
private
const
PREFERENCE_IDS
=
[
'default'
=>
self
::
NONE
,
'dmy'
=>
self
::
DMY
,
'mdy'
=>
self
::
MDY
,
'ymd'
=>
self
::
YMD
,
'ISO 8601'
=>
self
::
ISO
,
];
/** @var string[] Format strings similar to those used by date(), indexed by ID */
private
const
TARGET_FORMATS
=
[
self
::
MDY
=>
'F j, Y'
,
self
::
DMY
=>
'j F Y'
,
self
::
YMD
=>
'Y F j'
,
self
::
ISO
=>
'y-m-d'
,
self
::
YDM
=>
'Y, j F'
,
self
::
DM
=>
'j F'
,
self
::
MD
=>
'F j'
,
];
/** Used as a preference ID for rules that apply regardless of preference */
private
const
ALL
=
-
1
;
/** No preference: the date may be left in the same format as the input */
private
const
NONE
=
0
;
/** e.g. January 15, 2001 */
private
const
MDY
=
1
;
/** e.g. 15 January 2001 */
private
const
DMY
=
2
;
/** e.g. 2001 January 15 */
private
const
YMD
=
3
;
/** e.g. 2001-01-15 */
private
const
ISO
=
4
;
/** e.g. 2001, 15 January */
private
const
YDM
=
5
;
/** e.g. 15 January */
private
const
DM
=
6
;
/** e.g. January 15 */
private
const
MD
=
7
;
/**
* @param Language $lang In which language to format the date
*/
public
function
__construct
(
Language
$lang
)
{
$monthRegexParts
=
[];
for
(
$i
=
1
;
$i
<=
12
;
$i
++
)
{
$monthName
=
$lang
->
getMonthName
(
$i
);
$monthAbbrev
=
$lang
->
getMonthAbbreviation
(
$i
);
$this
->
monthNames
[
$i
]
=
$monthName
;
$monthRegexParts
[]
=
preg_quote
(
$monthName
,
'/'
);
$monthRegexParts
[]
=
preg_quote
(
$monthAbbrev
,
'/'
);
$this
->
xMonths
[
mb_strtolower
(
$monthName
)]
=
$i
;
$this
->
xMonths
[
mb_strtolower
(
$monthAbbrev
)]
=
$i
;
}
// Partial regular expressions
$monthNames
=
implode
(
'|'
,
$monthRegexParts
);
$dm
=
"(?<day>
\d
{1,2})[ _](?<monthName>{$monthNames})"
;
$md
=
"(?<monthName>{$monthNames})[ _](?<day>
\d
{1,2})"
;
$y
=
'(?<year>
\d
{1,4}([ _]BC|))'
;
$iso
=
'(?<isoYear>-?
\d
{4})-(?<isoMonth>
\d
{2})-(?<isoDay>
\d
{2})'
;
$this
->
regexes
=
[
self
::
DMY
=>
"/^{$dm}(?: *, *| +){$y}$/iu"
,
self
::
YDM
=>
"/^{$y}(?: *, *| +){$dm}$/iu"
,
self
::
MDY
=>
"/^{$md}(?: *, *| +){$y}$/iu"
,
self
::
YMD
=>
"/^{$y}(?: *, *| +){$md}$/iu"
,
self
::
DM
=>
"/^{$dm}$/iu"
,
self
::
MD
=>
"/^{$md}$/iu"
,
self
::
ISO
=>
"/^{$iso}$/iu"
,
];
}
/**
* Get a DateFormatter object
*
* @deprecated since 1.33 use MediaWikiServices::getDateFormatterFactory()
*
* @param Language|null $lang In which language to format the date
* Defaults to the site content language
* @return DateFormatter
*/
public
static
function
getInstance
(
?
Language
$lang
=
null
)
{
$lang
??=
MediaWikiServices
::
getInstance
()->
getContentLanguage
();
return
MediaWikiServices
::
getInstance
()->
getDateFormatterFactory
()->
get
(
$lang
);
}
/**
* @param string $preference User preference, must be one of "default",
* "dmy", "mdy", "ymd" or "ISO 8601".
* @param string $text Text to reformat
* @param array $options Ignored. Since 1.33, 'match-whole' is implied, and
* 'linked' has been removed.
*
* @return string
*/
public
function
reformat
(
$preference
,
$text
,
$options
=
[]
)
{
$userFormatId
=
self
::
PREFERENCE_IDS
[
$preference
]
??
self
::
NONE
;
foreach
(
self
::
TARGET_FORMATS
as
$source
=>
$_
)
{
if
(
isset
(
self
::
RULES
[
$userFormatId
][
$source
]
)
)
{
# Specific rules
$target
=
self
::
RULES
[
$userFormatId
][
$source
];
}
elseif
(
isset
(
self
::
RULES
[
self
::
ALL
][
$source
]
)
)
{
# General rules
$target
=
self
::
RULES
[
self
::
ALL
][
$source
];
}
elseif
(
$userFormatId
)
{
# User preference
$target
=
$userFormatId
;
}
else
{
# Default
$target
=
$source
;
}
$format
=
self
::
TARGET_FORMATS
[
$target
];
$regex
=
$this
->
regexes
[
$source
];
$text
=
preg_replace_callback
(
$regex
,
function
(
$match
)
use
(
$format
)
{
$text
=
''
;
// Pre-generate y/Y stuff because we need the year for the <span> title.
if
(
!
isset
(
$match
[
'isoYear'
]
)
&&
isset
(
$match
[
'year'
]
)
)
{
$match
[
'isoYear'
]
=
$this
->
makeIsoYear
(
$match
[
'year'
]
);
}
if
(
!
isset
(
$match
[
'year'
]
)
&&
isset
(
$match
[
'isoYear'
]
)
)
{
$match
[
'year'
]
=
$this
->
makeNormalYear
(
$match
[
'isoYear'
]
);
}
if
(
!
isset
(
$match
[
'isoMonth'
]
)
)
{
$m
=
$this
->
makeIsoMonth
(
$match
[
'monthName'
]
);
if
(
$m
===
null
)
{
// Fail
return
$match
[
0
];
}
$match
[
'isoMonth'
]
=
$m
;
}
if
(
!
isset
(
$match
[
'isoDay'
]
)
)
{
$match
[
'isoDay'
]
=
sprintf
(
'%02d'
,
$match
[
'day'
]
);
}
$formatLength
=
strlen
(
$format
);
for
(
$p
=
0
;
$p
<
$formatLength
;
$p
++
)
{
$char
=
$format
[
$p
];
switch
(
$char
)
{
case
'd'
:
// ISO day of month
$text
.=
$match
[
'isoDay'
];
break
;
case
'm'
:
// ISO month
$text
.=
$match
[
'isoMonth'
];
break
;
case
'y'
:
// ISO year
// @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset False positive
$text
.=
$match
[
'isoYear'
];
break
;
case
'j'
:
// ordinary day of month
if
(
!
isset
(
$match
[
'day'
]
)
)
{
$text
.=
intval
(
$match
[
'isoDay'
]
);
}
else
{
$text
.=
$match
[
'day'
];
}
break
;
case
'F'
:
// long month
$m
=
intval
(
$match
[
'isoMonth'
]
);
if
(
$m
>
12
||
$m
<
1
)
{
// Fail
return
$match
[
0
];
}
$text
.=
$this
->
monthNames
[
$m
];
break
;
case
'Y'
:
// ordinary (optional BC) year
// @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset False positive
$text
.=
$match
[
'year'
];
break
;
default
:
$text
.=
$char
;
}
}
$isoBits
=
[];
if
(
isset
(
$match
[
'isoYear'
]
)
)
{
$isoBits
[]
=
$match
[
'isoYear'
];
}
$isoBits
[]
=
$match
[
'isoMonth'
];
$isoBits
[]
=
$match
[
'isoDay'
];
$isoDate
=
implode
(
'-'
,
$isoBits
);
// Output is not strictly HTML (it's wikitext), but <span> is allowed.
return
Html
::
rawElement
(
'span'
,
[
'class'
=>
'mw-formatted-date'
,
'title'
=>
$isoDate
],
$text
);
},
$text
);
}
return
$text
;
}
/**
* @param string $monthName
* @return string|null 2-digit month number, e.g. "02", or null if the input was invalid
*/
private
function
makeIsoMonth
(
$monthName
)
{
$number
=
$this
->
xMonths
[
mb_strtolower
(
$monthName
)]
??
null
;
return
$number
!==
null
?
sprintf
(
'%02d'
,
$number
)
:
null
;
}
/**
* Make an ISO year from a year name, for instance: '-1199' from '1200 BC'
* @param string $year Year name
* @return string ISO year name
*/
private
function
makeIsoYear
(
$year
)
{
// Assumes the year is in a nice format, as enforced by the regex
if
(
substr
(
$year
,
-
2
)
==
'BC'
)
{
$num
=
intval
(
substr
(
$year
,
0
,
-
3
)
)
-
1
;
// PHP bug note: sprintf( "%04d", -1 ) fails poorly
$text
=
sprintf
(
'-%04d'
,
$num
);
}
else
{
$text
=
sprintf
(
'%04d'
,
$year
);
}
return
$text
;
}
/**
* Make a year from an ISO year, for instance: '400 BC' from '-0399'.
* @param string $iso ISO year
* @return int|string int representing year number in case of AD dates, or string containing
* year number and 'BC' at the end otherwise.
*/
private
function
makeNormalYear
(
$iso
)
{
if
(
$iso
<=
0
)
{
$text
=
(
intval
(
substr
(
$iso
,
1
)
)
+
1
)
.
' BC'
;
}
else
{
$text
=
intval
(
$iso
);
}
return
$text
;
}
}
/** @deprecated class alias since 1.43 */
class_alias
(
DateFormatter
::
class
,
'DateFormatter'
);
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Sat, May 16, 12:28 (1 d, 4 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
35/12/51031693ed56aa74c348902fe7b7
Default Alt Text
DateFormatter.php (9 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment