20th Century Studios Home Entertainment

**Twentieth Century Fox Home Entertainment LLC**, is the home video arm of 20th Century Studios and Searchlight Pictures.

In March 2019, The Walt Disney Company acquired 21st Century Fox. 20th Century Fox Home Entertainment now operates as a label of Walt Disney Studios Home Entertainment, and still releases titles from the other studios it has distribution deals with.

## History

### Magnetic Video and 20th Century-Fox Video

Magnetic Video was formed in 1976 by Andre Blay. Magnetic Video licensed 50 films from 20th Century Fox, including *The Sound of Music* and *Patton*, through Twentieth Century Fox Telecommunications. The films were released under the Magnetic Video banner on video cassette tapes and sold via a back page ad in TV Guide.

Blay sold Magnetic Video to 20th Century Fox in 1977 becoming the first studio home video division. Blay continued on as the subsidiary's president and CEO. Directly with Plitt Theatres chain in early 1980, they launched a pilot theater lobbies program. Through a distributor, a similar program was set up with United Artists Theaters.

In March 1982, Magnetic Video changed its name to 20th Century-Fox Video, Inc. while continued to be headquartered in Farmington Hills, Michigan. However, Blay was forced out at the time, with Telecommunications division president and CEO Steve Roberts took charge of 2CF Videos.

Roberts oversaw in June 1982 the merger of 20th Century-Fox Video operations and CBS Video Enterprises and continued as the joint venture president. He was replaced as CBS/Fox Video president in January 1983 by a former Columbia Pictures executive, Larry Hilford. Hilford was verbal critic of the video rental business, but with the studio looking at a likely loss, he attempted to make the out of the results. CBS/Fox and other home video units increased prices of the cassettes by around 67% to maximize income. They also move to encourage customer purchasing instead of renting. As a part of that CBS/Fox looked to existing retail chains for direct sales. Toys R Us and Child World signed the first direct deals in July 1985 with CBS/Fox. Walt Disney Home Video soon followed with a direct deal with Toys R Us.

by 1991, 20th Century Fox had put the CBS-Fox joint venture on the back burner and began releasing videocassette under the Fox Video. Fox Video was under president Bob DeLellis, a 1984 hire at CBS/Fox and rose to group vice president and president in 1991. With expected repeat viewing, Fox Video dropped prices on family films starting in June 1991 with '**Home Alone'** at a suggested list price of $24.98 to encourage purchasing over rental.

Fox Filmed Entertainment had a new chief operating officer and president Bill Mechanic in 1993 coming from Disney Home Video. Mechanic had plans to move Fox forward including Fox Video, but initial left DeLellis alone as he was setting up multiple creative divisions within Fox. Mechanic had been the one to install the "Vault" moratorium strategy at Disney. *Mrs. Doubtfire* was released soon after Mechanic's arrival with a sell through price and surpassed sale projections at 10 million tapes.

### Twentieth Century Fox Home Entertainment

The company was renamed Twentieth Century Fox Home Entertainment (also called Fox Home Entertainment) on March 16, 1995 with the addition to Fox Video of distribution operations, three other labels (Fox Kids Networks, CBS Videos, CBS/Fox Videos) and two new media units, Fox Interactive, Magnet Interactive Studios. Total revenue for the expanded business unit would have been over $800 million with Fox Videos providing the bulk at $650 million. Mechanic kept DeLellis as president of the expanded unit's North American operation with Jeff Yap as international president. By May 1995, Fox had Magnet under a worldwide label deal for 10 to 12 title through 1996. TCFHE would also be responsible for DVD when they hit the market.

Mechanic had Fox Home Entertainment institute the moratorium strategy with the August 1995 release of the three original *Star Wars* movies giving them a sales window before going off the market forever. Four months for *New Hope* and until the fall of 1997 for *The Empire Strikes Back* and *Return of the Jedi*. Sales topped 30 million copies over expectations. The company's 1996 release of *Independence Day* sold 18 million-unit making the industry's bestselling live-action home video release.

With the May 1997 departure of DeLellis, a quick rotation of presidents lead Fox Home Entertainment, Yapp for four months then an interim president Pat Wyatt, head of Twentieth Century Fox Licensing & Merchandising, in September 1997. With DVD being a Warner Home Video property, the company did not initial issue DVDs instead advocated for digital VHS tapes then the disposable Divx. Divx was DVD variant that had limited viewing time launched by Circuit City consumer electronics chain in June 1998. With DVD's low cost at $20 and Divx at $4.50, the DVD format won quickly out over Divx. News Corp chief Rupert Murdoch wanted a deal with Time Warner cable for the soon to launch Fox News and a lower dial position for Fox Family Channel, Mechanic adopted the DVD format to smooth the deal.

By 1998, Wyatt became permanent president of Twentieth Century Fox Home Entertainment. Wyatt then became head of Fox Consumer Product, which put together the video and licensing unit. Wyatt had to drop the licensing half as the home video unit boomed. DVD sales were so strong that they factored into green-lighting theatrical films.

Wyatt reorganized the Fox Home Entertainment through a partnership with replicator Cinram and lead the way in using data mining to increase its retail business partner's margins. Being ahead of the other studios, TCFHE began picking up additional outside labels as distribution clients with their fees covering the company's overhead. Fox Home Entertainment won multiple Vendor of the Year awards. Wyatt's system was a great edge for years.

TV-on-DVD business was intiated by Wyatt through the release of whole season of *The X-Files*, *The Simpsons* and *24*, which started the binge-watching concept. However, the videocassette rental business was declining such that video rental chains signed revenue-sharing deals with the studios, so additional copies of hits could be brought in a lower price and share sales for more customer satisfaction.

With the DVD boom, the company was able to have various promotional stunts for their video releases from *The Simpsons* and *Cast Away*. For the Simpsons release, there was *The Simpsons on Ice* in Bryant Park, turned the Empire State Building yellow and handed out yellow Santa hats that were all over town. *Cast Away* had a US Coast Guard rescue co-star ‘Wilson,’ the volleyball, at sea during their water safety awareness campaign.

Mechanic left Fox in June 2000 while Wyatt resigned in December 2002. Jim Gianopulos replaced Mechanic while executive vice president of domestic marketing and sales, Mike Dunn, took over from Wyatt. Wyatt left to start a direct-to-video film production and financing company for Japanese-style animated programming.

In 2004, 20th Century Fox passed on theatrical distribution, while picking up domestic home video rights on *The Passion of the Christ*. *Passion* sold 15 million DVDs. TCFHE continued obtaining additional Christian films' domestic home video rights for movie like "Mother Teresa" and the "Beyond the Gates of Splendor" documentary. After a 2005 test with a Fox Faith website, in 2006, 20th Century Fox Home Entertainment launched its own film production banner for religious films using the same name.

Effective October 1, 2005, 20th Century Fox Scandinavia was split into two, 20th Century Fox Theatrical Sweden and 20th Century Fox Home Entertainment Scandinavia. For the Home Entertainment Scandinavia, Peter Paumgardhen was appointed managing director and would report to senior vice president of 20th Century Fox Home Entertainment Europe Gary Ferguson.

DVD was declining in 2005 and high-definition TVs almost required a new disc format, Fox and half the studios back Blu-ray while the other half backed HD DVD and a couple planned to issue in both formats. Blu-ray won the format war in 2008, but with digital distribution starting, i.e. Netflix launching its streaming service, and the Great Recession, there was not the rebound that was expected.

With Metro-Goldwyn-Mayer moving its home video distribution to TCFHE in 2006, the company move into second place behind Warner Bros. and ahead of Walt Disney and had its best year yet. In October, Fox Home Entertainment issued the first to include a digital copy along on a disc with the special-edition DVD of *Live Free or Die Hard*.

In late 2006, the company began releasing its titles on Blu-ray.

2010 Blu-ray release of *Avatar* was the year's top-selling title and the top Blu-ray Disc seller with 5 million units sold. In 2011, Fox released on Blu-ray Disc the full *Star Wars* double trilogy on 9 discs premium set selling 1 million units its first week in stores generating $84 million in gross sales.

In response to Warner Brothers, Sony and MGM/UA issuing manufactured-on-demand lines of no-frills DVD-R editions of older films in May 2012, TCFHE began releasing its Cinema Archives series. By November 2012, the archive series had released 100 movies.

Fox Home Entertainment started the early window policy, where the digital version is release through digital retailers two or three weeks before the discs, and was launched with *Prometheus* in September 2012. This also started Fox's Digital HD program where customers could download or stream 600 Fox films on connected devices at less than $15/film through multiple major platforms. However, Digital HD was soon dropped as 4K, or Ultra HD, was introduced in 2012. In 2014, a high-tech think tank, Fox Innovation Lab, was formed under 20th Century Fox Home Entertainment.

In September 2015, the first Ultra HD Blu-ray player was introduced leading TCFHE to have future movies released same day in Ultra HD Blu-ray as regular Blu-ray and DVD. The first Ultra HD Blu-ray films were released in March 2016 with Fox being one of four studios involved and had the most titles with 10.

Dunn added another title in December 2016: president of product strategy and consumer business development. Dunn turned over TCFHE in March 2017 to Keith Feldman taking over his older title, president of worldwide home entertainment. Feldman was previously president of worldwide home entertainment distribution, and, before that, president of international.

### Disney acquisition

In December 2017, the acquisition of 21st Century Fox by Disney was proposed. After a Comcast bid and Disney counter bid approval was given. Disney took over most of 21st Century Fox on March 20, 2019.

On January 17, 2020, it was announced that the "Fox" name would be dropped from several of the Fox assets acquired by Disney, and the film unit would be renamed 20th Century Studios. However, the renaming of 20th Century Fox Television, 20th Television, Fox 21 Television Studios, 20th Century Fox Home Entertainment, Fox VFX Lab, 20th Century Fox Animation, Fox Digital Studio, Fox Studios Australia, Fox Music and Fox Networks Group International, was not made clear. As of May 2020, 20th Century Studios and Searchlight Pictures titles are distributed by Disney's Buena Vista Home Entertainment unit with a "Twentieth Century Fox Home Entertainment, LLC" copyright.

## Catalog

20th Century Fox Home Entertainment served as the Home Video distributor for a majority of releases from 20th Century Fox, Fox Searchlight Pictures, 20th Century Fox Television, Fox 21 Television Studios and other owned material.

Beginning in 1999, Fox became the main international home video distributor of Metro-Goldwyn-Mayer's catalogue, replacing Warner Home Video, which then was expanded to a domestic deal in 2006. TCFHE and MGM renewed their home video distribution deal in 2011 and June 2016, and will expire on June 30, 2020. MGM confirmed in their 2019–2020 report that they would not renew their deal with Fox after this deal and would find a replacement distributor for their catalog shortly after.

After a prior home entertainment distribution arrangement for Australia and Spain, in February 2016, Entertainment One (eOne) and 20th Century Fox Home Entertainment signed a new multi-territory distribution agreement. The agreement called for a distribution joint venture in Canada. In the UK, Ireland, the Netherlands, Belgium, Luxembourg, Spain and Australia, Fox would manage eOne's existing home video distribution. Entertainment One would eventually sign a worldwide distribution deal with Universal Pictures Home Entertainment shortly after Disney's purchase of Fox.

In the United States, the company was also the home video distributor for Relativity Media, EuropaCorp U.S.A., Annapurna Pictures and Yari Film Group. They also once served as the U.S. distributor for television and/or film products released by BBC Video until those North American distribution rights expired in 2000 and were transferred to Warner Home Video, and currently self-distribute their products. They also distributed HIT Entertainment releases in 2006 until 2008 when HIT moved their domestic distribution to Lionsgate Home Entertainment. The company also distributed products from American Greetings from 2007–2009, when they also moved distribution to Lionsgate Home Entertainment with the exception of Strawberry Shortcake, which remained under Fox. The company also released products from the DIC Entertainment catalogue in the United States from 2008 until 2013.

Since 1996, the UK division serves as the home media distributor of Pathé's movies and products from Guild Home Video. A similar deal also exists in France, through a joint venture with EuropaCorp known as Fox Pathé Europa.

Fox's best selling DVD titles are the various season box sets of *The Simpsons*.

## References

1. ↑ 1.00 1.01 1.02 1.03 1.04 1.05 1.06 1.07 1.08 1.09 1.10 1.11 1.12 1.13 1.14 1.15 1.16 1.17 1.18 1.19 Arnold, Thomas K.. "20th Century Fox Home Entertainment: A History of Distinction", *Media Play News*, April 21, 2019.
2. ↑ Amended Statement by Foreign Corporation, FoxVideo, Inc.. Califorina Secretary of State. Retrieved on June 17, 2019.
3. ↑ Goldstein, Seth. "20th Century Fox Forms Distrib Arm For Growing Bix", *Billboard*, Nielsen Business Media, Inc., May 6, 1995, pp. 7. (in en)
4. ↑ Munoz, Lorenza. "Fox Puts Faith in Christian Films", *Los Angeles Times*, September 19, 2006. Retrieved on June 18, 2019.
5. ↑ Lundberg, Pia. "20th Century Fox does the Scandi splits", *Variety*, September 11, 2005. Retrieved on June 18, 2019. (in en)
6. ↑ "20th Century Fox Announces Blu-ray Titles", Firstpost, September 1, 2006. Retrieved on December 16, 2014.
7. ↑ Kehr, Dave. "The Cinema Archives Series from 20th Century Fox", *The New York Times*, 2012-11-30. (in en-US)
8. ↑ Vary, Adam B. (January 17, 2020). Disney Drops Fox Name, Will Rebrand as 20th Century Studios, Searchlight Pictures. *Variety*. Retrieved on January 17, 2020.
9. ↑ "Downhill" Blu-Ray back cover..
10. ↑ 10.0 10.1 Hipes, Patrick (June 27, 2016). MGM & 20th Century Fox Renew Home Entertainment Deal. Retrieved on December 21, 2017.
11. ↑ https://d20qidnmpnrwiu.cloudfront.net/cdn/ff/2EkRrs42qi5\_owbYV38OTEhAVmft8wf4qe0Gy3rzkcU/1585084775/public/2020-03/YE%202019%20Financial%20Report.pdf
12. ↑ Milligan, Mercedes (February 24, 2016). Fox Home, eOne Ink Multi-Territory Pact. Retrieved on December 21, 2017.
13. ↑ https://deadline.com/2019/03/entertainment-one-eone-universal-pictures-home-entertainment-multi-territory-distribution-agreement-eddie-cunningham-steve-bertram-1202583216
14. ↑ Warner Bros. (June 28, 2000). BBC WORLDWIDE AMERICAS AND WARNER HOME VIDEO ANNOUNCE DISTRIBUTION DEAL.
15. ↑ https://animatedviews.com/2007/american-greetings-fox-expand-partnership/
16. ↑ https://www.crainscleveland.com/article/20090107/FREE/901079963/film-company-lionsgate-extends-relationship-with-american-greetings
17. ↑ The Simpsons - 'Don't have a cow man' - Season 4 press release! --[[ ----------------------------------

Lua module implementing the Template loop detected: Template:Webarchive template.

A merger of the functionality of three templates: Template:Wayback, Template:Webcite and Template:Cite archives

]]

--[[--------------------------< D E P E N D E N C I E S >------------------------------------------------------
]]

require('Module:No globals');
local getArgs = require ('Module:Arguments').getArgs;

--[[--------------------------< F O R W A R D D E C L A R A T I O N S >--------------------------------------
]]

local categories = {}; -- category names
local config = {}; -- global configuration settings
local digits = {}; -- for i18n; table that translates local-wiki digits to western digits
local err\_warn\_msgs = {}; -- error and warning messages
local excepted\_pages = {};
local month\_num = {}; -- for i18n; table that translates local-wiki month names to western digits
local prefixes = {}; -- service provider tail string prefixes
local services = {}; -- archive service provider data from
local s\_text = {}; -- table of static text strings used to build final rendering
local uncategorized\_namespaces = {}; -- list of namespaces that we should not categorize
local uncategorized\_subpages = {}; -- list of subpages that should not be categorized

--[[--------------------------< P A G E S C O P E I D E N T I F I E R S >----------------------------------
]]

local non\_western\_digits; -- boolean flag set true when data.digits.enable is true
local this\_page = mw.title.getCurrentTitle();

local track = {}; -- Associative array to hold tracking categories
local ulx = {}; -- Associative array to hold template data

--[[--------------------------< S U B S T I T U T E >----------------------------------------------------------

Populates numbered arguments in a message string using an argument table.

]]

local function substitute (msg, args)
 return args and mw.message.newRawMessage (msg, args):plain() or msg;
end

--[[--------------------------< tableLength >-----------------------

Given a 1-D table, return number of elements

]]

local function tableLength(T)
 local count = 0
 for \_ in pairs(T) do count = count + 1 end
 return count
end

--[=[-------------------------< M A K E \_ W I K I L I N K >----------------------------------------------------

Makes a wikilink; when both link and display text is provided, returns a wikilink in the form D; if only
link is provided, returns a wikilink in the form L; if neither are provided or link is omitted, returns an
empty string.

]=]

local function make\_wikilink (link, display, no\_link)
 if nil == no\_link then
 if link and ( *~= link) then*
 if display and ( *~= display) then*
 return table.concat ({'', display, ''});
 else
 return table.concat ({'', link, ''});
 end
 end
 return display or *; -- link not set so return the display text*

 else -- no\_link
 if display and ( *~= display) then -- if there is display text*
 return display; -- return that
 else
 return link or *; -- return the target article name or empty string*
 end
 end
end

--[[--------------------------< createTracking >-----------------------

Return data in track[] ie. tracking categories

]]

local function createTracking()
 if not excepted\_pages[this\_page.fullText] then -- namespace:title/fragment is allowed to be categorized (typically this module's / template's testcases page(s))
 if uncategorized\_namespaces[this\_page.nsText] then
 return *; -- this page not to be categorized so return empty string*
 end
 for \_,v in ipairs (uncategorized\_subpages) do -- cycle through page name patterns
 if this\_page.text:match (v) then -- test page name against each pattern
 return *; -- this subpage type not to be categorized so return empty string*
 end
 end
 end

 local out = {};
 if tableLength(track) > 0 then
 for key, \_ in pairs(track) do -- loop through table
 table.insert (out, make\_wikilink (key)); -- and convert category names to links
 end
 end
 return table.concat (out); -- concat into one big string; empty string if table is empty

end

--[[--------------------------< inlineError >-----------------------

Critical error. Render output completely in red. Add to tracking category.

This function called as the last thing before abandoning this module

]]

local function inlineError (msg, args)
 track[categories.error] = 1
 return table.concat ({
 'Error in ', -- open the error message span
 config.tname, -- insert the local language template name
 ' template: ',
 substitute (msg, args), -- insert the formatted error message
 '.', -- close the span
 createTracking() -- add the category
 })
end

--[[--------------------------< inlineRed >-----------------------

Render a text fragment in red, such as a warning as part of the final output.
Add tracking category.

 ]]

local function inlineRed(msg, trackmsg)
 if trackmsg == "warning" then
 track[categories.warning] = 1;
 elseif trackmsg == "error" then
 track[categories.error] = 1;
 end

 return '' .. msg .. ''
end

--[[--------------------------< base62 >-----------------------

Convert base-62 to base-10
Credit: https://de.wikipedia.org/wiki/Modul:Expr

]]

local function base62( value )
 local r = 1 -- default return value is input value is malformed

 if value:match ('%W') then -- value must only be in the set [0-9a-zA-Z]
 return; -- nil return when value contains extraneous characters
 end

 local n = #value -- number of characters in value
 local k = 1
 local c
 r = 0
 for i = n, 1, -1 do -- loop through all characters in value from ls digit to ms digit
 c = value:byte( i, i )
 if c >= 48 and c <= 57 then -- character is digit 0-9
 c = c - 48
 elseif c >= 65 and c <= 90 then -- character is ascii a-z
 c = c - 55
 else -- must be ascii A-Z
 c = c - 61
 end
 r = r + c \* k -- accumulate this base62 character's value
 k = k \* 62 -- bump for next
 end -- for i

 return r
end

--[[--------------------------< D E C O D E \_ D A T E >--------------------------------------------------------

Given a date string, return it in iso format along with an indicator of the date's format. Except that month names
must be recognizable as legitimate month names with proper capitalization, and that the date string must match one
of the recognized date formats, no error checking is done here; return nil else

]]

local function decode\_date (date\_str)
 local patterns = {
 ['dmy'] = {'^(%d%d?) +([^%s%d]+) +(%d%d%d%d)$', 'd', 'm', 'y'}, -- %a does not recognize unicode combining characters used by some languages
 ['mdy'] = {'^([^%s%d]+) (%d%d?), +(%d%d%d%d)$', 'm', 'd', 'y'},
 ['ymd'] = {'^(%d%d%d%d) +([^%s%d]+) (%d%d?)$', 'y', 'm', 'd'}, -- not mos compliant at en.wiki but may be acceptible at other wikis
 };

 local t = {};

 if non\_western\_digits then -- this wiki uses non-western digits?
 date\_str = mw.ustring.gsub (date\_str, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 if date\_str:match ('^%d%d%d%d%-%d%d%-%d%d$') then -- already an iso format date, return western digits form
 return date\_str, 'iso';
 end

 for k, v in pairs (patterns) do
 local c1, c2, c3 = mw.ustring.match (date\_str, patterns[k][1]); -- c1 .. c3 are captured but we don't know what they hold

 if c1 then -- set on match
 t = { -- translate unspecified captures to y, m, and d
 [patterns[k][2]] = c1, -- fill the table of captures with the captures
 [patterns[k][3]] = c2, -- take index names from src\_pattern table and assign sequential captures
 [patterns[k][4]] = c3,
 };
 if month\_num[t.m] then -- when month not already a number
 t.m = month\_num[t.m]; -- replace valid month name with a number
 else
 return nil, 'iso'; -- not a valid date form because month not valid
 end

 return mw.ustring.format ('%.4d-%.2d-%.2d', t.y, t.m, t.d), k; -- return date in iso format
 end
 end
 return nil, 'iso'; -- date could not be decoded; return nil and default iso date
end

--[[--------------------------< makeDate >-----------------------

Given year, month, day numbers, (zero-padded or not) return a full date in df format
where df may be one of:
 mdy, dmy, iso, ymd

on entry, year, month, day are presumed to be correct for the date that they represent; all are required

in this module, makeDate() is sometimes given an iso-format date in year:
 makeDate (2018-09-20, nil, nil, df)
this works because table.concat() sees only one table member

]]

local function makeDate (year, month, day, df)
 local format = {
 ['dmy'] = 'j F Y',
 ['mdy'] = 'F j, Y',
 ['ymd'] = 'Y F j',
 ['iso'] = 'Y-m-d',
 };

 local date = table.concat ({year, month, day}, '-'); -- assemble year-initial numeric-format date (zero padding not required here)

 if non\_western\_digits then -- this wiki uses non-western digits?
 date = mw.ustring.gsub (date, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 return mw.getContentLanguage():formatDate (format[df], date);
end

--[[--------------------------< I S \_ V A L I D \_ D A T E >----------------------------------------------------

Returns true if date is after 31 December 1899 (why is 1900 the min year? shouldn't the internet's date-of-birth
be min year?), not after today's date, and represents a valid date (29 February 2017 is not a valid date). Applies
Gregorian leapyear rules.

all arguments are required

]]

local function is\_valid\_date (year, month, day)
 local days\_in\_month = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
 local month\_length;
 local y, m, d;
 local today = os.date ('\*t'); -- fetch a table of current date parts

 if not year or  *== year or not month or*  == month or not day or  *== day then*
 return false; -- something missing
 end

 y = tonumber (year);
 m = tonumber (month);
 d = tonumber (day);

 if 1900 > y or today.year < y or 1 > m or 12 < m then -- year and month are within bounds TODO: 1900?
 return false;
 end

 if (2==m) then -- if February
 month\_length = 28; -- then 28 days unless
 if (0==(y%4) and (0~=(y%100) or 0==(y%400))) then -- is a leap year?
 month\_length = 29; -- if leap year then 29 days in February
 end
 else
 month\_length=days\_in\_month[m];
 end

 if 1 > d or month\_length < d then -- day is within bounds
 return false;
 end
 -- here when date parts represent a valid date
 return os.time({['year']=y, ['month']=m, ['day']=d, ['hour']=0}) <= os.time(); -- date at midnight must be less than or equal to current date/time
end

--[[--------------------------< decodeWebciteDate >-----------------------

Given a URI-path to Webcite (eg. /67xHmVFWP) return the encoded date in df format

returns date string in df format - webcite date is a unix timestamp encoded as bae62
or the string 'query'

]]

local function decodeWebciteDate(path, df)

 local dt = {};
 local decode;

 dt = mw.text.split(path, "/")

 -- valid URL formats that are not base62

 -- http://www.webcitation.org/query?id=1138911916587475
 -- http://www.webcitation.org/query?url=http..&date=2012-06-01+21:40:03
 -- http://www.webcitation.org/1138911916587475
 -- http://www.webcitation.org/cache/73e53dd1f16cf8c5da298418d2a6e452870cf50e
 -- http://www.webcitation.org/getfile.php?fileid=1c46e791d68e89e12d0c2532cc3cf629b8bc8c8e

 if dt[2]:find ('query', 1, true) or
 dt[2]:find ('cache', 1, true) or
 dt[2]:find ('getfile', 1, true) or
 tonumber(dt[2]) then
 return 'query';
 end

 decode = base62(dt[2]); -- base62 string -> exponential number
 if not decode then
 return nil; -- nil return when dt[2] contains characters not in %w
 end
 dt = os.date('\*t', string.format("%d", decode):sub(1,10)) -- exponential number -> text -> first 10 characters (a unix timestamp) -> a table of date parts

 decode = makeDate (dt.year, dt.month, dt.day, 'iso'); -- date comparisons are all done in iso format with western digits
 if non\_western\_digits then -- this wiki uses non-western digits?
 decode = mw.ustring.gsub (decode, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 return decode;
end

--[[--------------------------< decodeWaybackDate >-----------------------

Given a URI-path to Wayback (eg. /web/20160901010101/http://example.com )
or Library of Congress Web Archives (eg. /all/20160901010101/http://example.com)
or UK Government Web Archive (eg. /ukgwa/20160901010101/http://example.com or /tna/20160901010101/http://example.com)

return the formatted date eg. "September 1, 2016" in df format
Handle non-digits in snapshot ID such as "re\_" and "-" and "\*"

 valid date string in df format - wayback date is valid (including the text string 'index' when date is '/\*/')
 empty string - wayback date is malformed (less than 8 digits, not a valid date)
 nil - wayback date is '/save/' or otherwise not a number

 second return value is an appropriate 'message' may or may not be formatted

]]

local function decodeWaybackDate(path, df)

 local msg, snapdate;

 snapdate = path:gsub ('^/web/', *):gsub ('^/all/',* ):gsub ('^/ukgwa/', *):gsub ('^/tna/',* ):gsub ('^/', *); -- remove leading /web/, /all/, /ukgwa/, /tna/, or /*
 snapdate = snapdate:match ('^[^/]+'); -- get timestamp
 if snapdate == "\*" then -- eg. /web/\*/http.., etc.
 return 'index'; -- return indicator that this url has an index date
 end

 snapdate = snapdate:gsub ('%a%a\_%d?$', *):gsub ('%-',* ); -- from date, remove any trailing "re\_", dashes

 msg = *;*
 if snapdate:match ('%\*$') then -- a trailing '\*' causes calendar display at archive .org
 snapdate = snapdate:gsub ('%\*$', *); -- remove so not part of length calc later*
 msg = inlineRed (err\_warn\_msgs.ts\_cal, 'warning'); -- make a message
 end

 if not tonumber(snapdate) then
 return nil, 'ts\_nan'; -- return nil (fatal error flag) and message selector
 end

 local dlen = snapdate:len();
 if dlen < 8 then -- we need 8 digits TODO: but shouldn't this be testing for 14 digits?
 return *, inlineRed (err\_warn\_msgs.ts\_short, 'error'); -- return empty string and error message*
 end

 local year, month, day = snapdate:match ('(%d%d%d%d)(%d%d)(%d%d)'); -- no need for snapdatelong here

 if not is\_valid\_date (year, month, day) then
 return *, inlineRed (err\_warn\_msgs.ts\_date, 'error'); -- return empty string and error message*
 end

 snapdate = table.concat ({year, month, day}, '-'); -- date comparisons are all done in iso format
 if 14 == dlen then
 return snapdate, msg; -- return date with message if any
 else
 return snapdate, msg .. inlineRed (err\_warn\_msgs.ts\_len, 'warning'); -- return date with warning message(s)
 end
end

--[[--------------------------< decodeArchiveisDate >-----------------------

Given an Archive.is "long link" URI-path (e.g. /2016.08.28-144552/http://example.com)
return the date in df format (e.g. if df = dmy, return 28 August 2016)
Handles "." and "-" in snapshot date, so 2016.08.28-144552 is same as 20160828144552

 valid date string in df format - archive.is date is valid (including the text string 'short link' when url is the short form)
 empty string - wayback date is malformed (not a number, less than 8 digits, not a valid date)
 nil - wayback date is '/save/'

 second return value is an appropriate 'message' may or may not be formatted

]]

local function decodeArchiveisDate(path, df)
 local snapdate

 if path:match ('^/%w+$') then -- short form url path is '/' followed by some number of base 62 digits and nothing else
 return "short link" -- e.g. http://archive.is/hD1qz
 end

 snapdate = mw.text.split (path, '/')[2]:gsub('[%.%-]', *); -- get snapshot date, e.g. 2016.08.28-144552; remove periods and hyphens*

 local dlen = string.len(snapdate)
 if dlen < 8 then -- we need 8 digits TODO: but shouldn't this be testing for 14 digits?
 return *, inlineRed (err\_warn\_msgs.ts\_short, 'error'); -- return empty string and error message*
 end

 local year, month, day = snapdate:match ('(%d%d%d%d)(%d%d)(%d%d)'); -- no need for snapdatelong here

 if not is\_valid\_date (year, month, day) then
 return *, inlineRed (err\_warn\_msgs.ts\_date, 'error'); -- return empty string and error message*
 end

 snapdate = table.concat ({year, month, day}, '-'); -- date comparisons are all done in iso format
 if 14 == dlen then
 return snapdate; -- return date
 else
 return snapdate, inlineRed (err\_warn\_msgs.ts\_len, 'warning'); -- return date with warning message
 end
 end

--[[--------------------------< serviceName >-----------------------

Given a domain extracted by mw.uri.new() (eg. web.archive.org) set tail string and service ID

]]

local function serviceName(host, no\_link)
 local tracking;
 local index;

 host = host:lower():gsub ('^web%.(.+)', '%1'):gsub ('^www%.(.+)', '%1'); -- lowercase, remove web. and www. subdomains

 if services[host] then
 index = host;
 else
 for k, \_ in pairs (services) do
 if host:find ('%f[%a]'..k:gsub ('([%.%-])', '%%%1')) then
 index = k;
 break;
 end
 end
 end

 if index then
 local out = {*}; -- empty string in [1] so that concatenated result has leading single space*
 ulx.url1.service = services[index][4] or 'other';
 tracking = services[index][5] or categories.other;
 -- build tail string
 if false == services[index][1] then -- select prefix
 table.insert (out, prefixes.at);
 elseif true == services[index][1] then
 table.insert (out, prefixes.atthe);
 else
 table.insert (out, services[index][1]);
 end

 table.insert (out, make\_wikilink (services[index][2], services[index][3], no\_link)); -- add article wikilink
 if services[index][6] then -- add tail postfix if it exists
 table.insert (out, services[index][6]);
 end

 ulx.url1.tail = table.concat (out, ' '); -- put it all together; result has leading space character

 else -- here when unknown archive
 ulx.url1.service = 'other';
 tracking = categories.unknown;
 ulx.url1.tail = table.concat ({*, prefixes.at, host, inlineRed (err\_warn\_msgs.unknown\_url, error)}, ' ');*
 end

 track[tracking] = 1
end

--[[--------------------------< parseExtraArgs >-----------------------

Parse numbered arguments starting at 2, such as url2..url10, date2..date10, title2..title10
 For example: Template loop detected: Template:Webarchive
 Three url arguments not in numeric sequence (1..4..7).
 Function only processes arguments numbered 2 or greater (in this case 4 and 7)
 It creates numeric sequenced table entries like:
 urlx.url2.url = <argument value for url4>
 urlx.url3.url = <argument value for url7>
 Returns the number of URL arguments found numbered 2 or greater (in this case returns "2")

 ]]

local function parseExtraArgs(args)

 local i, j, argurl, argurl2, argdate, argtitle

 j = 2
 for i = 2, config.maxurls do
 argurl = "url" .. i
 if args[argurl] then
 argurl2 = "url" .. j
 ulx[argurl2] = {}
 ulx[argurl2]["url"] = args[argurl]
 argdate = "date" .. i
 if args[argdate] then
 ulx[argurl2]["date"] = args[argdate]
 else
 ulx[argurl2]["date"] = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 end

 argtitle = "title" .. i
 if args[argtitle] then
 ulx[argurl2]["title"] = args[argtitle]
 else
 ulx[argurl2]["title"] = nil
 end
 j = j + 1
 end
 end

 if j == 2 then
 return 0
 else
 return j - 2
 end
end

--[[--------------------------< comma >-----------------------

Given a date string, return "," if it's MDY

]]

local function comma(date)
 return (date and date:match ('%a+ +%d%d?(,) +%d%d%d%d')) or *;*
end

--[[--------------------------< createRendering >-----------------------

Return a rendering of the data in ulx[][]

]]

local function createRendering()

 local displayfield
 local out = {};

 local index\_date, msg = ulx.url1.date:match ('(index)(.\*)'); -- when ulx.url1.date extract 'index' text and message text (if there is a message)
 ulx.url1.date = ulx.url1.date:gsub ('index.\*', 'index'); -- remove message

 if 'none' == ulx.url1.format then -- For Template:Wayback, Template:Webcite
 table.insert (out, '['); -- open extlink markup
 table.insert (out, ulx.url1.url); -- add url

 if ulx.url1.title then
 table.insert (out, ' ') -- the required space
 table.insert (out, ulx.url1.title) -- the title
 table.insert (out, ']'); -- close extlink markup
 table.insert (out, ulx.url1.tail); -- tail text
 if ulx.url1.date then
 table.insert (out, ' ('); -- open date text; TODO: why the html entity? replace with regular space?
 table.insert (out, 'index' == ulx.url1.date and s\_text.archive or s\_text.archived); -- add text
 table.insert (out, ' '); -- insert a space
 table.insert (out, ulx.url1.date); -- add date
 table.insert (out, ')'); -- close date text
 end
 else -- no title
 if index\_date then -- when url date is 'index'
 table.insert (out, table.concat ({' ', s\_text.Archive\_index, ']'})); -- add the index link label
 table.insert (out, msg or *); -- add date mismatch message when url date is /\*/ and |date= has valid date*
 else
 table.insert (out, table.concat ({' ', s\_text.Archived, '] '})); -- add link label for url has timestamp date (will include mismatch message if there is one)
 end
 if ulx.url1.date then
 if 'index' ~= ulx.url1.date then
 table.insert (out, ulx.url1.date); -- add date when data is not 'index'
 end
 table.insert (out, comma(ulx.url1.date)); -- add ',' if date format is mdy
 table.insert (out, ulx.url1.tail); -- add tail text
 else -- no date
 table.insert (out, ulx.url1.tail); -- add tail text
 end
 end

 if 0 < ulx.url1.extraurls then -- For multiple archive URLs
 local tot = ulx.url1.extraurls + 1
 table.insert (out, '.') -- terminate first url
 table.insert (out, table.concat ({' ', s\_text.addlarchives, ': '})); -- add header text

 for i=2, tot do -- loop through the additionals
 local index = table.concat ({'url', i}); -- make an index
 displayfield = ulx[index]['title'] and 'title' or 'date'; -- choose display text
 table.insert (out, '['); -- open extlink markup
 table.insert (out, ulx[index]['url']); -- add the url
 table.insert (out, ' '); -- the required space
 table.insert (out, ulx[index][displayfield]); -- add the label
 table.insert (out, ']'); -- close extlink markup
 table.insert (out, i==tot and '.' or ', '); -- add terminator
 end
 end
 return table.concat (out); -- make a big string and done

 else -- For Template:Cite archives
 if 'addlarchives' == ulx.url1.format then -- Multiple archive services
 table.insert (out, table.concat ({s\_text.addlarchives, ': '})); -- add header text
 else -- Multiple pages from the same archive
 table.insert (out, table.concat ({s\_text.addlpages, ' '})); -- add header text
 table.insert (out, ulx.url1.date); -- add date to header text
 table.insert (out, ': '); -- close header text
 end

 local tot = ulx.url1.extraurls + 1;
 for i=1, tot do -- loop through the additionals
 local index = table.concat ({'url', i}); -- make an index
 table.insert (out, '['); -- open extlink markup
 table.insert (out, ulx[index]['url']); -- add url
 table.insert (out, ' '); -- add required space

 displayfield = ulx[index]['title'];
 if 'addlarchives' == ulx.url1.format then
 if not displayfield then
 displayfield = ulx[index]['date']
 end
 else -- must be addlpages
 if not displayfield then
 displayfield = table.concat ({s\_text.Page, ' ', i});
 end
 end
 table.insert (out, displayfield); -- add title, date, page label text
 table.insert (out, ']'); -- close extlink markup
 table.insert (out, (i==tot and '.' or ', ')); -- add terminator
 end
 return table.concat (out); -- make a big string and done
 end
end

--[[--------------------------< P A R A M E T E R \_ N A M E \_ X L A T E >--------------------------------------

for internaltionalization, translate local-language parameter names to their English equivalents

TODO: return error message if multiple aliases of the same canonical parameter name are found?

returns two tables:
 new\_args - holds canonical form parameters and their values either from translation or because the parameter was already in canonical form
 origin - maps canonical-form parameter names to their untranslated (local language) form for error messaging in the local language

unrecognized parameters are ignored

]]

local function parameter\_name\_xlate (args, params, enum\_params)
 local name; -- holds modifiable name of the parameter name during evaluation
 local enum; -- for enumerated parameters, holds the enumerator during evaluation
 local found = false; -- flag used to break out of nested for loops
 local new\_args = {}; -- a table that holds canonical and translated parameter k/v pairs
 local origin = {}; -- a table that maps original (local language) parameter names to their canonical name for local language error messaging
 local unnamed\_params; -- set true when unsupported positional parameters are detected

 for k, v in pairs (args) do -- loop through all of the arguments in the args table
 name = k; -- copy of original parameter name

 if 'string' == type (k) then
 if non\_western\_digits then -- true when non-western digits supported at this wiki
 name = mw.ustring.gsub (name, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 enum = name:match ('%d+$'); -- get parameter enumerator if it exists; nil else

 if not enum then -- no enumerator so looking for non-enumnerated parameters
 -- TODO: insert shortcut here? if params[name] then name holds the canonical parameter name; no need to search further
 for pname, aliases in pairs (params) do -- loop through each parameter the params table
 for \_, alias in ipairs (aliases) do -- loop through each alias in the parameter's aliases table
 if name == alias then
 new\_args[pname] = v; -- create a new entry in the new\_args table
 origin [pname] = k; -- create an entry to make canonical parameter name to original local language parameter name
 found = true; -- flag so that we can break out of these nested for loops
 break; -- no need to search the rest of the aliases table for name so go on to the next k, v pair
 end
 end

 if found then -- true when we found an alias that matched name
 found = false; -- reset the flag
 break; -- go do next args k/v pair
 end
 end
 else -- enumerated parameters
 name = name:gsub ('%d$', '#'); -- replace enumeration digits with place holder for table search
 -- TODO: insert shortcut here? if num\_params[name] then name holds the canonical parameter name; no need to search further
 for pname, aliases in pairs (enum\_params) do -- loop through each parameter the num\_params table
 for \_, alias in ipairs (aliases) do -- loop through each alias in the parameter's aliases table
 if name == alias then
 pname = pname:gsub ('#$', enum); -- replace the '#' place holder with the actual enumerator
 new\_args[pname] = v; -- create a new entry in the new\_args table
 origin [pname] = k; -- create an entry to make canonical parameter name to original local language parameter name
 found = true; -- flag so that we can break out of these nested for loops
 break; -- no need to search the rest of the aliases table for name so go on to the next k, v pair
 end
 end

 if found then -- true when we found an alias that matched name
 found = false; -- reset the flag
 break; -- go do next args k/v pair
 end
 end
 end
 else
 unnamed\_params = true; -- flag for unsupported positional parameters
 end
 end -- for k, v
 return new\_args, origin, unnamed\_params;
end

--[[--------------------------< W E B A R C H I V E >----------------------------------------------------------

template entry point

]]

local function webarchive(frame)
 local args = getArgs (frame);

 local data = mw.loadData (table.concat ({ -- make a data module name; sandbox or live
 'Module:Webarchive/data',
 frame:getTitle():find('sandbox', 1, true) and '/sandbox' or  *-- this instance is ./sandbox then append /sandbox*
 }));
 categories = data.categories; -- fill in the forward declarations
 config = data.config;
 if data.digits.enable then
 digits = data.digits; -- for i18n; table of digits in the local wiki's language
 non\_western\_digits = true; -- use\_non\_western\_digits
 end
 err\_warn\_msgs = data.err\_warn\_msgs;
 excepted\_pages = data.excepted\_pages;
 month\_num = data.month\_num; -- for i18n; table of month names in the local wiki's language
 prefixes = data.prefixes;
 services = data.services;
 s\_text = data.s\_text;
 uncategorized\_namespaces = data.uncategorized\_namespaces;
 uncategorized\_subpages = data.uncategorized\_subpages;

 local origin = {}; -- holds a map of English to local language parameter names used in the current template; not currently used
 local unnamed\_params; -- boolean set to true when template call has unnamed parameters
 args, origin, unnamed\_params = parameter\_name\_xlate (args, data.params, data.enum\_params); -- translate parameter names in args to English

 local date, format, msg, udate, uri, url;
 local ldf = 'iso'; -- when there is no |date= parameter, render url dates in iso format

 if args.url and args.url1 then -- URL argument (first)
 return inlineError (data.crit\_err\_msgs.conflicting, {origin.url, origin.url1});
 end

 url = args.url or args.url1;

 if not url then
 return inlineError (data.crit\_err\_msgs.empty);
 end
 -- these iabot bugs perportedly fixed; removing these causes lua script error
--[[ -- at Template:Webarchive/testcases/Production; resolve that before deleting these tests
 if mw.ustring.find( url, "https://web.http", 1, true ) then -- track bug - TODO: IAbot bug; not known if the bug has been fixed; deferred
 track[categories.error] = 1;
 return inlineError (data.crit\_err\_msgs.iabot1);
 end
 if url == "https://web.archive.org/http:/" then -- track bug - TODO: IAbot bug; not known if the bug has been fixed; deferred
 track[categories.error] = 1;
 return inlineError (data.crit\_err\_msgs.iabot2);
 end
]]

 if not (url:lower():find ('^http') or url:find ('^//')) then
 return inlineError (data.crit\_err\_msgs.invalid\_url );
 end

 ulx.url1 = {}
 ulx.url1.url = url

 ulx.url1.extraurls = parseExtraArgs(args)

 local good = false;
 good, uri = pcall (mw.uri.new, ulx.url1.url); -- get a table of uri parts from this url; protected mode to prevent lua error when ulx.url1.url is malformed

 if not good or nil == uri.host then -- abandon when ulx.url1.url is malformed
 return inlineError (data.crit\_err\_msgs.invalid\_url);
 end

 serviceName(uri.host, args.nolink)

 if args.date and args.date1 then -- Date argument
 return inlineError (data.crit\_err\_msgs.conflicting, {origin.date, origin.date1});
 end

 date = args.date or args.date1;
 date = date and date:gsub (' +', ' '); -- replace multiple spaces with a single space

 if date and config.verifydates then
 if '\*' == date then
 date = 'index';
 ldf = 'iso'; -- set to default format
 elseif 'mdy' == date then
 date = nil; -- if date extracted from URL,
 ldf = 'mdy'; -- then |date=mdy overrides iso
 elseif 'dmy' == date then
 date = nil; -- if date extracted from URL,
 ldf = 'dmy'; -- then |date=dmy overrides iso
 elseif 'ymd' == date then
 date = nil; -- if date extracted from URL,
 ldf = 'ymd'; -- then |date=ymd overrides iso
 else
 date, ldf = decode\_date (date); -- get an iso format date from date and get date's original format
 end
 end

 if 'wayback' == ulx.url1.service or 'locwebarchives' == ulx.url1.service or 'ukgwa' == ulx.url1.service then
 if date then
 if config.verifydates then
 if ldf then
 udate, msg = decodeWaybackDate (uri.path); -- get the url date in iso format and format of date in |date=; 'index' when wayback url date is \*
 if not udate then -- this is the only 'fatal' error return
 return inlineError (data.crit\_err\_msgs[msg]);
 end

 if udate ~= date then -- date comparison using iso format dates
 date = udate;
 msg = table.concat ({
 inlineRed (err\_warn\_msgs.mismatch, 'warning'), -- add warning message
 msg, -- add message if there is one
 });
 end
 end
 end
 else -- no |date=
 udate, msg = decodeWaybackDate (uri.path);

 if not udate then -- this is the only 'fatal' error return
 return inlineError (data.crit\_err\_msgs[msg]);
 end

 if  *== udate then*
 date = nil; -- unset
 else
 date = udate;
 end
 end

 elseif 'webcite' == ulx.url1.service then
 if date then
 if config.verifydates then
 if ldf then
 udate = decodeWebciteDate (uri.path); -- get the url date in iso format
 if 'query' ~= udate then -- skip if query
 if udate ~= date then -- date comparison using iso format dates
 date = udate;
 msg = table.concat ({
 inlineRed (err\_warn\_msgs.mismatch, 'warning'),
 });
 end
 end
 end
 end
 else
 date = decodeWebciteDate( uri.path, "iso" )
 if date == "query" then
 date = nil; -- unset
 msg = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 elseif not date then -- invalid base62 string
 date = inlineRed (err\_warn\_msgs.date1, 'error');
 end
 end

 elseif 'archiveis' == ulx.url1.service then
 if date then
 if config.verifydates then
 if ldf then
 udate, msg = decodeArchiveisDate (uri.path) -- get the url date in iso format
 if 'short link' ~= udate then -- skip if short link
 if udate ~= date then -- date comparison using iso format dates
 date = udate;
 msg = table.concat ({
 inlineRed (err\_warn\_msgs.mismatch, 'warning'), -- add warning message
 msg, -- add message if there is one
 });
 end
 end
 end
 end
 else -- no |date=
 udate, msg = decodeArchiveisDate( uri.path, "iso" )
 if udate == "short link" then
 date = nil; -- unset
 msg = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 elseif  *== udate then*
 date = nil; -- unset
 else
 date = udate;
 end
 end

 else -- some other service
 if not date then
 msg = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 end
 end

 if 'index' == date then
 ulx.url1.date = date .. (msg or *); -- create index + message (if there is one)*
 elseif date then
 ulx.url1.date = makeDate (date, nil, nil, ldf) .. (msg or *); -- create a date in the wiki's local language + message (if there is one)*
 else
 ulx.url1.date = msg;
 end

 format = args.format; -- Format argument

 if not format then
 format = "none"
 else
 for k, v in pairs (data.format\_vals) do -- |format= accepts two specific values loop through a table of those values
 local found; -- declare a nil flag
 for \_, p in ipairs (v) do -- loop through local language variants
 if format == p then -- when |format= value matches
 format = k; -- use name from table key
 found = true; -- declare found so that we can break out of outer for loop
 break; -- break out of inner for loop
 end
 end

 if found then
 break;
 end
 end

 if format == "addlpages" then
 if not ulx.url1.date then
 format = "none"
 end
 elseif format == "addlarchives" then
 format = "addlarchives"
 else
 format = "none"
 end
 end
 ulx.url1.format = format

 if args.title and args.title1 then -- Title argument
 return inlineError (data.crit\_err\_msgs.conflicting, {origin.title, origin.title1});
 end

 ulx.url1.title = args.title or args.title1;

 local rend = createRendering()
 if not rend then
 return inlineError (data.crit\_err\_msgs.unknown);
 end

 return rend .. ((unnamed\_params and inlineRed (err\_warn\_msgs.unnamed\_params, 'warning')) or *) .. createTracking();*

end

--[[--------------------------< E X P O R T E D F U N C T I O N S >------------------------------------------
]]

return {webarchive = webarchive};

## External links

* Template:URL
* 20th Century Fox Home Entertainment at the Internet Movie Database
* Twentieth Century Fox Home Entertainment at the Internet Movie Database