For faster navigation, this Iframe is preloading the Wikiwand page for 模組:Date table sorting.

模組:Date table sorting

文档图示 模块文档[查看] [编辑] [历史] [清除缓存]

此模块实现((Date table sorting))。用法详见模板页面。

local yesno = require('Module:Yesno')
local lang = mw.language.getContentLanguage()
local N_YEAR_DIGITS = 12
local MAX_YEAR = 10^N_YEAR_DIGITS - 1

--------------------------------------------------------------------------------
-- Dts class
--------------------------------------------------------------------------------

local Dts = {}

function Dts.GetErrMsgFromOther (frame, args) 
	local msg = ''
	local args = {}
	if frame == mw.getCurrentFrame() then
		msg = frame.args[1] or frame.args['1'] or ''
		args[1] = frame.args[2] or frame.args['2'] or ''
		args[2] = frame.args[3] or frame.args['3'] or ''
		args[3] = frame.args[4] or frame.args['4'] or ''
		args['cat'] = 0
	else
		return Dts.errmsg (frame, args)
	end
	return Dts.errmsg (msg, args)
end

function Dts.errmsg (msg, args) 
	msg = msg or ''
	args = args or {}
	local needcat = args['cat'] or 1

	local function arg (x)
		if args[x] then
			return args[x]
		end
		return ''
	end
	
	local msglist = {
		['error'] = '<strong class="error">[[Module:Date table sorting]]錯誤:%s</strong>',
		
		['valid-err'] = '解析日期格式「%s」時出現未知錯誤。',
		['valid-year'] = '給出的年份「%s」年不合理。',
		['valid-month'] = '給出的月份「%s」月不合理。',
		['valid-day'] = '給出的日「%s」日不合理。',
		['valid-bc'] = '給出的西元前後判斷值「%s」不合理(僅能使用「BC」, 「BCE」, 「AD」 或「CE」)。',
		['valid-date'] = '給出的日期「%s」不合理。',
		['valid-args-addkey'] = '參數<code>addkey</code>的值「%s」不合理。',
		
		['year-zero'] = '年份不得為零。',
		['year-min'] = '給出的年份「%s年」低於最小值%s年。',
		['year-max'] = '給出的年份「%s年」超出最大值%s年。',
		['year-integer'] = '給出的年份「%s年」不是整數。',
		
		['unknown-month'] = '月份只有1月到12月,沒有「第%s月」!',
		['unknown-day-31'] = '%s月只有31天,沒有「第%s天」!',
		['unknown-day-30'] = '%s月只有30天,沒有「第%s天」!',
		['unknown-day-Feb'] = '%s年2月只有%s天,沒有「第%s天」!',
		
		['unknown-format'] = '無法識別格式「%s」。',
		
		['args-addkey'] = '參數<code>addkey</code>的值應介於0到9999之間。'
	}
	return string.format((msglist[msg] or ''), (args[1] or ''), (args[2] or ''), (args[3] or ''), (args[4] or ''))
end

Dts.__index = Dts

Dts.months = {
	"January",
	"February",
	"March",
	"April",
	"May",
	"June",
	"July",
	"August",
	"September",
	"October",
	"November",
	"December"
}

Dts.monthsAbbr = {
	"Jan",
	"Feb",
	"Mar",
	"Apr",
	"May",
	"Jun",
	"Jul",
	"Aug",
	"Sep",
	"Oct",
	"Nov",
	"Dec"
}

function Dts._makeMonthSearch(t)
	local ret = {}
	for i, month in ipairs(t) do
		ret[month:lower()] = i
	end
	return ret
end
Dts.monthSearch = Dts._makeMonthSearch(Dts.months)
Dts.monthSearchAbbr = Dts._makeMonthSearch(Dts.monthsAbbr)
Dts.monthSearchAbbr['sept'] = 9 -- Allow "Sept" to match September

Dts.formats = {
	ymd = true,
	dmy = true,
	mdy = true,
	ym = true,
	dm = true,
	md = true,
	my = true,
	y = true,
	m = true,
	d = true,
	hide = true
}

function Dts.new(args)
	local self = setmetatable({}, Dts)

	-- Parse date parameters.
	-- In this step we also record whether the date was in DMY or YMD format,
	-- and whether the month name was abbreviated.
	if args[2] or args[3] or args[4] then
		self:parseDateParts(args[1], args[2], args[3], args[4])
	elseif args[1] then
		self:parseDate(args[1])
	end

	-- Raise an error on invalid values
	if self.year then
		if self.year == 0 then
			error(self.errmsg('year-zero') , 0)
		elseif self.year < -MAX_YEAR then
			error(self.errmsg('year-min', {self.year, lang:formatNum(-MAX_YEAR)}), 0)
		elseif self.year > MAX_YEAR then
			error(self.errmsg('year-max', {self.year, lang:formatNum(MAX_YEAR)}), 0)
		elseif math.floor(self.year) ~= self.year then
			error(self.errmsg('year-integer', {self.year}), 0)
		end
	end
	if self.month and (
		self.month < 1
		or self.month > 12
		or math.floor(self.month) ~= self.month
	) then
		error(self.errmsg('unknown-month', {self.month}), 0)
	end
	
	if self.day then
		if (
			(self.month == 1) 
			or (self.month == 3)
			or (self.month == 5)
			or (self.month == 7)
			or (self.month == 8)
			or (self.month == 10)
			or (self.month == 12)
		) and (self.day > 31) then
			error(self.errmsg('unknown-day-31', {self.month, self.day}), 0)
		elseif (
			(self.month == 4) 
			or (self.month == 6)
			or (self.month == 9)
			or (self.month == 11)
		) and (self.day > 30) then
			error(self.errmsg('unknown-day-30', {self.month, self.day}), 0)
		elseif (self.month == 2) and (self.day > 29) and (self.year % 400 == 0 or self.year % 4 == 0 and self.year % 100 ~= 0) then
			error(self.errmsg('unknown-day-Feb', {self.year, 29, self.day}), 0)
		elseif (self.month == 2) and (self.day > 28) and not (self.year % 400 == 0 or self.year % 4 == 0 and self.year % 100 ~= 0) then
			error(self.errmsg('unknown-day-Feb', {self.year, 28, self.day}), 0)
		end
	end
	--]=]

	-- Set debug mode
	if args.debug then
		self.isdebug = args.debug
	end

	-- Set the format string
	if args.format then
		self.format = args.format
	else
		self.format = self.format or 'ymd'
	end
	if not Dts.formats[self.format] then
		error(self.errmsg('unknown-format', {tostring(self.format)}), 0)
	end

	-- Set addkey. This adds a value at the end of the sort key, allowing users
	-- to manually distinguish between identical dates.
	if args.addkey then
		self.addkey = tonumber(args.addkey)
		if not self.addkey or
			math.floor(self.addkey) ~= self.addkey
		then
			error(self.errmsg('valid-args-addkey', {args.addkey}), 0)
		elseif self.addkey < 0 or self.addkey > 9999 then
			error(self.errmsg('args-addkey'), 0)
		end
	end

	-- Set whether the displayed date is allowed to wrap or not.
	self.isWrapping = args.nowrap == 'off' or yesno(args.nowrap) == false

	-- Set whether the abbreviated or not.
	self.isAbbreviated = args.abbr == 'on' or yesno(args.abbr) == true

	-- Check for deprecated parameters.
	if args.link then
		self.hasDeprecatedParameters = true
	end

	return self
end

function Dts:hasDate()
	return (self.year or self.month or self.day) ~= nil
end

-- Find the month number for a month name, and set the isAbbreviated flag as
-- appropriate.
function Dts:parseMonthName(s)
	s = s:lower()
	local month = Dts.monthSearch[s]
	if month then
		return month
	else
		month = Dts.monthSearchAbbr[s]
		if month then
			self.isAbbreviated = true
			return month
		end
	end
	return nil
end

-- Parses separate parameters for year, month, day, and era.
function Dts:parseDateParts(year, month, day, bc)
	if year then
		self.year = tonumber(year)
		if not self.year then
			error(self.errmsg('valid-year', {tostring(year)}), 0)
		end
	end
	if month then
		if tonumber(month) then
			self.month = tonumber(month)
		elseif type(month) == 'string' then
			self.month = self:parseMonthName(month)
		end
		if not self.month then
			error(self.errmsg('valid-month', {tostring(month)}), 0)
		end
	end
	if day then
		self.day = tonumber(day)
		if not self.day then
			error(self.errmsg('valid-day', {tostring(day)}))
		end
	end
	if bc then
		local bcLower = type(bc) == 'string' and bc:lower()
		if bcLower == 'bc' or bcLower == 'bce' then
			if self.year and self.year > 0 then
				self.year = -self.year
			end
		elseif bcLower ~= 'ad' and bcLower ~= 'ce' then
			error(self.errmsg('valid-bc', {tostring(bc)}), 0)
		end
	end
end

-- This method parses date strings. This is a poor man's alternative to
-- mw.language:formatDate, but it ends up being easier for us to parse the date
-- here than to use mw.language:formatDate and then try to figure out after the
-- fact whether the month was abbreviated and whether we were DMY or MDY.
function Dts:parseDate(date)
	-- Generic error message.
	local function dateError()
		error(self.errmsg('valid-date', {tostring(date)}), 0)
	end

	local function parseDayOrMonth(s)
		if s:find('^%d%d?$') then
			return tonumber(s)
		end
	end

	local function parseMonth(s)
		if s:find('^%d%d?$') and tonumber(s) >=1 and tonumber(s) <= 12 then
			return tonumber(s)
		end
	end

	local function parseDay(s)
		if self.month then
			lastday = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
			if s:find('^%d%d?$') and tonumber(s) >=1 and tonumber(s) <= lastday[self.month] then
				return tonumber(s)
			end
		end
	end

	local function parseYear(s)
		if s:find('^%d%d?%d?%d?$') then
			return tonumber(s)
		end
	end

	-- Deal with year-only dates first, as they can have hyphens in, and later
	-- we need to split the string by all non-word characters, including
	-- hyphens. Also, we don't need to restrict years to 3 or 4 digits, as on
	-- their own they can't be confused as a day or a month number.
	self.year = tonumber(date)
	if self.year then
		return
	end

	-- Split the string using non-word characters as boundaries.
	date = tostring(date)
	local parts = mw.text.split(date, '%W+')
	local nParts = #parts
	if parts[1] == '' or parts[nParts] == '' or nParts > 3 then
		-- We are parsing a maximum of three elements, so raise an error if we
		-- have more. If the first or last elements were blank, then the start
		-- or end of the string was a non-word character, which we will also
		-- treat as an error.
		dateError()
	elseif nParts < 1 then
		-- If we have less than one element, then something has gone horribly
		-- wrong.
		error(self.errmsg('valid-err', {tostring(date)}), 0)
	end

	if nParts == 1 then
		-- This can be either a month name or a year.
		self.month = self:parseMonthName(parts[1])
		if not self.month then
			self.year = parseYear(parts[1])
			if not self.year then
				dateError()
			end
		end
	elseif nParts == 2 then
		-- This can be any of the following formats:
		-- DD Month
		-- Month DD
		-- Month YYYY
		-- YYYY-MM
		-- MM-DD
		self.month = self:parseMonthName(parts[1])
		if self.month then
			-- This is either Month DD or Month YYYY.
			self.year = parseYear(parts[2])
			if not self.year then
				-- This is Month DD.
				self.format = 'mdy'
				self.day = parseDayOrMonth(parts[2])
				if not self.day then
					dateError()
				end
			end
		else
			self.month = self:parseMonthName(parts[2])
			if self.month then
				-- This is DD Month.
				self.format = 'ymd'
				self.day = parseDayOrMonth(parts[1])
				if not self.day then
					dateError()
				end
			else
				-- This is MM-DD.
				self.month = parseMonth(parts[1])
				if self.month then
					self.day = parseDay(parts[2])
				else
					-- This is YYYY-MM.
					self.year = parseYear(parts[1])
					self.month = parseMonth(parts[2])
					if not (self.year and self.month) then
						dateError()
					end
				end
			end
		end
	elseif nParts == 3 then
		-- This can be any of the following formats:
		-- DD Month YYYY
		-- Month DD, YYYY
		-- YYYY-MM-DD
		-- DD-MM-YYYY
		self.month = self:parseMonthName(parts[1])
		if self.month then
			-- This is Month DD, YYYY.
			self.format = 'mdy'
			self.day = parseDayOrMonth(parts[2])
			self.year = parseYear(parts[3])
			if not self.day or not self.year then
				dateError()
			end
		else
			self.day = parseDayOrMonth(parts[1])
			if self.day then
				self.month = self:parseMonthName(parts[2])
				if self.month then
					-- This is DD Month YYYY.
					self.format = 'ymd'
					self.year = parseYear(parts[3])
					if not self.year then
						dateError()
					end
				else
					-- This is DD-MM-YYYY.
					self.format = 'ymd'
					self.month = parseDayOrMonth(parts[2])
					self.year = parseYear(parts[3])
					if not self.month or not self.year then
						dateError()
					end
				end
			else
				-- This is YYYY-MM-DD
				self.year = parseYear(parts[1])
				self.month = parseDayOrMonth(parts[2])
				self.day = parseDayOrMonth(parts[3])
				if not self.year or not self.month or not self.day then
					dateError()
				end
			end
		end
	end
end

function Dts:makeSortKey()
	local year, month, day
	local nYearDigits = N_YEAR_DIGITS
	if self:hasDate() then
		year = self.year or os.date("*t").year
		if year < 0 then
			year = -MAX_YEAR - 1 - year
			nYearDigits = nYearDigits + 1 -- For the minus sign
		end
		month = self.month or 1
		day = self.day or 1
	else
		-- Blank ((dts)) transclusions should sort last.
		year = MAX_YEAR
		month = 99
		day = 99
	end
	return string.format(
		'%0' .. nYearDigits .. 'd-%02d-%02d-%04d',
		year, month, day, self.addkey or 0
	)
end

function Dts:getMonthName()
	if not self.month then
		return ''
	end
	if self.isAbbreviated then
		return self.monthsAbbr[self.month]
	else
		return self.months[self.month]
	end
end

function Dts:makeDisplay()
	if self.format == 'hide' then
		return ''
	end
	local hasYear = self.year and self.format:find('y')
	local hasMonth = self.month and self.format:find('m')
	local hasDay = self.day and self.format:find('d')
	local ret = {}
	if hasYear then
		if self.year < 0 then
			ret[#ret + 1] = '公元前'
		end
		local displayYear = math.abs(self.year)
		displayYear = displayYear > 9999 and lang:formatNum(displayYear) or tostring(displayYear)
		ret[#ret + 1] = displayYear
		ret[#ret + 1] = '年'
	end
	if hasMonth then
		ret[#ret + 1] = self.month
		ret[#ret + 1] = '月'
	end
	if hasDay then
		ret[#ret + 1] = self.day
		ret[#ret + 1] = '日'
	end
	return table.concat(ret)
end

function Dts:makeDisplayAbbr()
	if self.format == 'hide' then
		return ''
	end
	local hasYear = self.year and self.format:find('y')
	local hasMonth = self.month and self.format:find('m')
	local hasDay = self.day and self.format:find('d')
	local ret = {}
	if hasYear then
		if self.year < 0 then
			ret[#ret + 1] = 'BC'
		end
		local displayYear = math.abs(self.year)
		displayYear = displayYear > 9999 and lang:formatNum(displayYear) or tostring(displayYear)
		ret[#ret + 1] = displayYear
		if hasMonth or hasDay then
			ret[#ret + 1] = '/'
		end
	end
	if hasMonth then
		ret[#ret + 1] = self.month
		if hasDay then
			ret[#ret + 1] = '/'
		end
	end
	if hasDay then
		ret[#ret + 1] = self.day
	end
	return table.concat(ret)
end

function Dts:makeDisplayAbbrOrNoAbbr()
	if self.isAbbreviated then
		return self:makeDisplayAbbr()
	else
		return self:makeDisplay()
	end
end

function Dts:renderTrackingCategories()
	if self.hasDeprecatedParameters then
		return '[[Category:Template:Date_table_sorting錯誤|廢]]'
	else
		return ''
	end
end

function Dts:__tostring()
	local root = mw.html.create()

	-- Sort key
	if self.isdebug then
		root:tag('span')
			:css('border', 'dotted 1px')
			:wikitext(self:makeSortKey())
	else
		root:tag('span')
			:addClass('sortkey')
			:css('display', 'none')
			:css('speak', 'none')
			:wikitext(self:makeSortKey())
	end
	
	-- Display
	if self:hasDate() then
		if self.isWrapping then
			root:wikitext(self:makeDisplayAbbrOrNoAbbr())
		else
			root:tag('span')
				:css('white-space', 'nowrap')
				:wikitext(self:makeDisplayAbbrOrNoAbbr())
		end
	end

	-- Tracking categories
	root:wikitext(self:renderTrackingCategories())

	return tostring(root)
end

--------------------------------------------------------------------------------
-- Exports
--------------------------------------------------------------------------------

local p = {}

function p._exportClasses()
	return {
		Dts = Dts
	}
end

p.errmsg = Dts.GetErrMsgFromOther

function p._main(args)
	--由於技術問題,無法處理中文的年月日,因此將其丟進 Module:Date_Convert 轉成 ISODate 再傳入,就不會錯
	-- not tonumber(args[1]) 排掉年分,((fact|可以解析的2000-01-01))
	if args[1] and not tonumber(args[1]) then
		--iferror預防丟入 Module:Date_Convert 的格式不支援但 Module:Date_table_sorting 支援
		args[1] = mw.getCurrentFrame():callParserFunction('#iferror', require('Module:Date_Convert')._converttime(args[1]), args[1])
	end
	--end
	local success, ret = pcall(function ()
		local dts = Dts.new(args)
		return tostring(dts)
	end)
	if success then
		return ret
	else
		ret = Dts.errmsg('error', {ret})
		if mw.title.getCurrentTitle().namespace == 0 then
			-- Only categorise in the main namespace
			ret = ret .. '[[Category:Template:Date_table_sorting錯誤]]'
		end
		return ret
	end
end

function p.main(frame)
	local args = require('Module:Arguments').getArgs(frame, {
		wrappers = 'Template:Date table sorting',
	})
	return p._main(args)
end

return p
{{bottomLinkPreText}} {{bottomLinkText}}
模組:Date table sorting
Listen to this article

This browser is not supported by Wikiwand :(
Wikiwand requires a browser with modern capabilities in order to provide you with the best reading experience.
Please download and use one of the following browsers:

This article was just edited, click to reload
This article has been deleted on Wikipedia (Why?)

Back to homepage

Please click Add in the dialog above
Please click Allow in the top-left corner,
then click Install Now in the dialog
Please click Open in the download dialog,
then click Install
Please click the "Downloads" icon in the Safari toolbar, open the first download in the list,
then click Install
{{::$root.activation.text}}

Install Wikiwand

Install on Chrome Install on Firefox
Don't forget to rate us

Tell your friends about Wikiwand!

Gmail Facebook Twitter Link

Enjoying Wikiwand?

Tell your friends and spread the love:
Share on Gmail Share on Facebook Share on Twitter Share on Buffer

Our magic isn't perfect

You can help our automatic cover photo selection by reporting an unsuitable photo.

This photo is visually disturbing This photo is not a good choice

Thank you for helping!


Your input will affect cover photo selection, along with input from other users.

X

Get ready for Wikiwand 2.0 🎉! the new version arrives on September 1st! Don't want to wait?