Módulo:Hours

De Wikiviajes, la guía libre de viajes
Ir a la navegación Ir a la búsqueda
Icono de documentación de módulo Documentación del módulo[ver] [editar] [historial] [purgar]

Version check

Designación de la versión en Wikidata: 2021-04-08 Ok!

Uso

This module provides the opening hours of an object from Wikidata. Its mostly used in the module VCard, but can used separately as well via {{#invoke: Hours | getHours }}.

Examples

ID Label Opening hours
Q99477686 Chuchichäschtli Abierto: diario 5:00 pm–12:00 am
Q6373 British Museum Abierto: diario 10:00 am–5:00 pm; Cerrado: 24. Dic., 25. Dic., 26. Dic., 1. Ene.
Q219867 Bahnhof King’s Cross Abierto: Lun–Vie 5:00 am–01:36; sab 5:00 am–01:11; dom 5:30 am–01:36
Q2292227 Skigebiet Gaißau-Hintersee Abierto: diario 9:00 am–4:00 pm (15 de diciembre–15 de marzo)
Q11323320 Pablo Gargallo Museum Abierto: mar, mie, jue, vie, sab 10:00 am–2:00 pm, 5:00 pm–9:00 pm; dom, Feriado legal 10:00 am–2:30 pm
Q27831262 Öömrang Hüs Abierto: Lun–Vie 11:00 am–1:30 pm (Temporada alta, Temporada baja); Cerrado: sab, dom, Feriado legal
Q99690036 Nationalpark-Tor Rurberg Abierto: diario 9:00 am–1:00 pm, 1:30 pm–5:00 pm (1. Abr.–31. Oct.), 10:00 am–1:00 pm, 1:30 pm–4:00 pm (1. Nov.–31. Mar.)
Q2583681 Grand Egyptian Museum Abierto: cerrado temporalmente
Esta documentación está transcluida desde Módulo:Hours/doc.
Los editores pueden experimentar en la zona de pruebas (crear) y en los casos de prueba (crear) del módulo.
Por favor, añade las categorías en la subpágina de documentación. Subpáginas de este módulo.

-- getting opening hours from Wikidata

-- documentation
local Hours = {
	suite  = 'Hours',
	serial = '2021-04-08',
	item   = 99600452
}

-- module import
require( 'Module:No globals' )
local hi = require( 'Module:Hours/i18n' )
local wu = require( 'Module:Wikidata utilities' )

-- module variable and administration
local hr = {
	moduleInterface = Hours
}

-- local variables
local categIds    = {}
local labelTables = { hi.dateIds }
local showOptions = {}

local function isSet( s )
	return s and s ~= ''
end

-- insert a value into a table only if it is set
local function tableInsert( tab, value )
	if isSet( value ) then
		table.insert( tab, value )
	end
end

-- value count for any variable
local function getCount( tab )
	return type( tab ) == 'table' and #tab or 0
end

local function getLabelFromTables( id )
	local label
	for _, tab in ipairs( labelTables ) do
		label = tab[ id ]
		if label then
			break
		end
	end
	return label
end

-- getting normalized time hh:dd
local function getNormalizedTime( s )
	local count
	s, count = mw.ustring.gsub( s, hi.texts.timePattern, '%1:%2' )
	return ( count > 0 ) and s or nil
end

function hr.formatTime( s )
	local t = getNormalizedTime( s )
	if not t then
		return s
	end

	local formatStr = hi.texts.formatTime
	t = mw.text.split( t, ':', true )
	if #t == 1 then
		t[ 2 ] = '00'
	end
	if hi.options.hour12 then
		local isAM = true
		local n = tonumber( t[ 1 ] )
		if n > 12 then
			isAM = false
			t[ 1 ] = '' .. ( n - 12 )
		end
		formatStr = isAM and hi.texts.formatAM or hi.texts.formatPM
	end
	s = mw.ustring.format( formatStr, mw.text.trim( t[ 1 ] ),
		mw.text.trim( t[ 2 ] ) )
	if hi.options.leadingZero then
		s = s:gsub( '^(%d):', '0%1:' )
	else
		s = s:gsub( '^0(%d):', '%1:' )
	end
	if hi.options.removeZeros then
		s = s:gsub( '^(%d%d?):00', '%1' )
	end
	return s
end

-- getting label for a qualifier id
-- to save computing time firstly the id will fetched from Hours/i18n table
-- if available, otherwise from Wikidata
local function getLabelFromId( id, wikilang, fallbackLang )
	if not isSet( id ) then
		return ''
	end

	-- from table
	local label = getLabelFromTables( id )

	-- from Wikidata
	if not label and mw.wikibase.isValidEntityId( id ) then
		label = wu.getLabel( id, wikilang )
		if not label and isSet( fallbackLang ) then
			label = wu.getLabel( id, fallbackLang )
			if label then
				categIds.fallbackLabel = ''
			end
		end
		if label then
			categIds.labelFromWikidata = ''
		end
	end

	-- abbreviate labels
	if isSet( label ) then
		for _, abbr in ipairs( hi.abbr ) do
			label = mw.ustring.gsub( label, abbr.f, abbr.a )
		end
	end

	-- additional time formatting
	if isSet( label ) then
		if hi.months then
			for full, short in pairs( hi.months ) do
				label = mw.ustring.gsub( label, full, short )
			end
		end
		label = hr.formatTime( label )
	end
	return label or ''
end

local function abbreviateTimeStr( s, all, pattern, repl )
	if not isSet( s ) or not isSet( pattern ) or not repl then
        return s or ''
	end
	if all then
		s = mw.ustring.gsub( s, pattern, repl )
	else
		local matchPattern = mw.ustring.gsub( pattern, '%(%%d%)', '' )
		local first, stop = mw.ustring.find( s, pattern )
		if first then
			local second = mw.ustring.find( s, pattern, stop + 1 )
			if second and mw.ustring.match( s, matchPattern ) ==
				mw.ustring.match( s, matchPattern, stop + 1 ) then
        		s = mw.ustring.gsub( s, pattern, repl, 1 )
        	end
    	end
    end
    return s
end

-- getting time period string
-- i:  position in from and to arrays
-- id: label for P3035 value
local function getTimePeriod( from, to, i, id )
	local result = ''
	if id and ( id == getLabelFromTables( hi.times.daily )
		or id == getLabelFromTables( hi.times.is24_7 ) )
		and from and to and from[ i ] == getLabelFromTables( hi.times.Jan1 ) and
		to[ i ] == getLabelFromTables( hi.times.Dec31 ) then
		return ''
	end
	if from and isSet( from[ i ] ) and to and isSet( to[ i ] ) then
		result = mw.ustring.format( hi.texts.fromTo, from[ i ], to[ i ] )
		if isSet( hi.texts.hourPattern ) then
			result = abbreviateTimeStr( result, hi.texts.hourReplAll,
				hi.texts.hourPattern, hi.texts.hourRepl )
		end
	elseif from and isSet( from[ i ] ) then
		result = mw.ustring.format( hi.texts.from, from[ i ] )
	elseif to and isSet( to[ i ] ) then
		result = mw.ustring.format( hi.texts.to, to[ i ] )
	end
	return result
end

-- collecting all maintenance categories
function hr.getCategories( formatStr )
	local result = wu.getCategories( formatStr )
	for k, _ in pairs( categIds ) do
		result = result .. ( hi.categories[ k ] or hi.categories.unknownError )
	end
	if showOptions.demo then
		-- remove category links
		result = result:gsub( '%[%[[^%[]*%]%]', '' )
	end
	return result
end

-- getting a string with listed days at which an establishment is closed
local function getClosed( arr )
	return ( arr and #arr > 0 ) and mw.ustring.format( hi.texts.closed or '%s', 
		table.concat( arr, hi.texts.separator ) ) or ''
end

-- converting day range from Mo, Tu, We to Mo–We, and so on
local function getRange( arr, minPos, maxPos )
	if maxPos > 0 and minPos > 0 and maxPos > minPos then
		arr[ minPos ] = mw.ustring.format( hi.texts.fromTo, arr[ minPos ],
			arr[ maxPos ] )
		for i = maxPos, minPos + 1, -1 do
			table.remove( arr, i )
		end
	end
end

-- looking for day ranges like Mo, Tu, We and so on and converting them
local function convertDayRange( arr )
	local count = #arr
	local minPos = 0
	local maxPos = 0
	local value, valueMin
	while count > 0 do
		value = hi.weekdays[ arr[ count ] ]
		if not value then
			getRange( arr, minPos, maxPos )
			maxPos = 0
		elseif maxPos == 0 then
			maxPos = count
			valueMin = value
		elseif maxPos > 0 and value == valueMin - 1 then
			minPos = count
			valueMin = value
		else
			getRange( arr, minPos, maxPos )
			maxPos = 0
		end
		count = count - 1
	end
	getRange( arr, minPos, maxPos )
end

-- concating non-empty strings
local function concatStrings( sep, str1, str2 )
	local tab = {}
	tableInsert( tab, str1 )
	tableInsert( tab, str2 )
	return table.concat( tab, sep )
end

-- add comment if not yet exists
local function addComment( tab, value )
	if not isSet( value ) then
		return
	elseif #tab == 0 then
		table.insert( tab, value )
	else
		for i = 1, #tab, 1 do
			if tab[ i ] == value then
				break
			end
			if i == #tab then
				table.insert( tab, value )
			end
		end
	end
end

-- get lastEdit from reference retrieve date
function hr.getLastedit( lastEdit, statements )
	local isBoolean = type( lastEdit ) == 'boolean'
	if isBoolean and lastEdit == false then
		return lastEdit
	end
	local le = ''
	for _, statement in ipairs( statements ) do
		if statement.references then
			for _, reference in ipairs( statement.references ) do
				if reference[ hi.wd.retrieved ] then
					for _, retrieved in ipairs( reference[ hi.wd.retrieved ] ) do
						retrieved = wu.getDateFromTime( retrieved )
						if retrieved > le then
							le = retrieved
						end
					end
				end
			end
		end
	end
	if isBoolean then
		return ( le ~= '' ) and le or lastEdit
	else
		return ( le > lastEdit ) and le or lastEdit
	end
end

-- main function for usage in Lua modules
-- entity: entity id or entity table
-- wikilang: content language of the wiki
-- fallbackLang: optional additional language for fallback
-- formatStr: optional format string for property categories
-- show: table of show options (addCategories, msg, nomsg) or nil
-- lastedit: dat of last edit. If false no date will be fetched
-- labelTabel: additional table with Q-id label pairs
function hr.getHoursFromWikidata( entity, wikilang, fallbackLang, formatStr,
	show, lastEdit, labelTable )

	-- collecting days at which an establishment is closed
	local closeDays = {}
	local closeDaysHelper = {}
	local function mergeDays( days )
		if not days or #days == 0 then
			return
		end
		for _, day in ipairs( days ) do
			if not closeDaysHelper[ day ] then
				table.insert( closeDays, day )
				closeDaysHelper[ day ] = ''
			end
		end
	end
	local function clearDays()
		closeDays = {}
		closeDaysHelper = {}
	end

	-- adding additional properties if an additional Q-id table is given
	if labelTable then
		table.insert( labelTables, labelTable )
		for _, p in ipairs( hi.wd.addComments ) do
			table.insert( hi.wd.all, p )
			table.insert( hi.wd.comments, p )
			table.insert( hi.wd.commentsForClosed, p )
		end
	end

	-- preparing show options
	showOptions = show or {}
	showOptions.addCategories = hi.options.addCategories
	if showOptions.msg then
		showOptions.addCategories = true
	elseif showOptions.nomsg then
		showOptions.addCategories = false
	end

	-- format string for property categories
	if not isSet( formatStr ) then
		formatStr = hi.categories.properties
	end

	-- 1st step: getting statements for P3025: open days
	local statements = wu.getValuesWithQualifiers( entity, hi.wd.opened, nil,
		hi.wd.all, hi.wd.retrieved, nil, getLabelFromId, wikilang, fallbackLang )
	lastEdit = hr.getLastedit( lastEdit, statements )

	-- converting statements to human-readable strings
	local result = {}
	local comments, s
	local is24_7 = getLabelFromTables( hi.times.is24_7 )

	for _, statement in ipairs( statements ) do
		-- opening times
		local times = {}
		local count = math.max( getCount( statement[ hi.wd.hourOpen ] ),
			getCount( statement[ hi.wd.hourClosed ] ) )
		if count > 0 then
			for j = 1, count, 1 do
				s = getTimePeriod( statement[ hi.wd.hourOpen ],
					statement[ hi.wd.hourClosed ], j )
				if isSet( s ) then
					table.insert( times, s )
				elseif statement.value ~= is24_7 then
					categIds.withoutTime = ''
				end
			end
		elseif statement.value ~= is24_7 then
			categIds.withoutTime = ''
		end
		s = table.concat( times, hi.texts.separator )

		-- comments
		comments = {}
		count = math.max( getCount( statement[ hi.wd.dayOpen ] ),
			getCount( statement[ hi.wd.dayClosed ] ) )
		for j = 1, count, 1 do
			addComment( comments,
				getTimePeriod( statement[ hi.wd.dayOpen ],
					statement[ hi.wd.dayClosed ], j, statement.value ) )
		end
		for _, key in ipairs( hi.wd.comments ) do
			if statement[ key ] then
				addComment( comments, table.concat( statement[ key ],
					hi.texts.separator ) )
			end
		end

		-- concating times and comments
		times = {}
		tableInsert( times, s )
		s = table.concat( comments, hi.texts.separator )
		if isSet( s ) then
			table.insert( times, mw.ustring.format( hi.texts.comment, s ) )
		end

		local item = {}
		tableInsert( item, table.concat( times, hi.texts.joiner ) )

		-- close statements (P3026) as qualifiers for open days property (P3025)
		mergeDays( statement[ hi.wd.closed ] )
		if not hi.options.clusterClosed and ( i == #statements or
			statements[ i ].value ~= statements[ i + 1 ].value ) then
			convertDayRange( closeDays )
			tableInsert( item, getClosed( closeDays ) )
			clearDays()
		end
		s = table.concat( item, hi.texts.separator )
	
		-- copying each statement to result table
		if statement.sort2 == 1 then
			tableInsert( result, { value = { statement.value }, times = s } )
		elseif s ~= '' then
			result[ #result ].times = concatStrings( hi.texts.separator,
				result[ #result ].times, s )
		end
	end

	-- checking for duplicates
	for i = #result, 2, -1 do
		if result[ i ].times == result[ i - 1 ].times then
			for _, value in ipairs( result[ i ].value ) do
				table.insert( result[ i - 1 ].value, value )
			end
			table.remove( result, i )
		end
	end

	-- converting day range
	for i = 1, #result, 1 do
		local arr = result[ i ].value
		convertDayRange( arr )
		result[ i ] = concatStrings( hi.texts.joiner,
			table.concat( arr, hi.texts.separator ), result[ i ].times )
	end

	-- 2nd step: getting separated close statements (P3026)
	local statements = wu.getValuesWithQualifiers( entity, hi.wd.closed, nil,
		hi.wd.commentsForClosed, hi.wd.retrieved, nil, getLabelFromId, wikilang,
		fallbackLang )
	if #statements > 0 then
		lastEdit = hr.getLastedit( lastEdit, statements )

		-- getting closed values
		local closed = {}	
		for _, statement in ipairs( statements ) do
			local closedDate = {}
			table.insert( closedDate, statement.value )

			-- getting comments
			comments = {}
			for _, key in ipairs( hi.wd.commentsForClosed ) do
				if statement[ key ] then
					addComment( comments, table.concat( statement[ key ],
						hi.texts.separator ) )
				end
			end
			s = table.concat( comments, hi.texts.separator )
			if isSet( s ) then
				table.insert( closedDate, mw.ustring.format( hi.texts.comment, s ) )
			end

			table.insert( closed, table.concat( closedDate, hi.texts.joiner ) )
		end
		mergeDays( closed )
	end
	convertDayRange( closeDays )
	tableInsert( result, getClosed( closeDays ) )

	-- 3rd step: adding additional statements
	local statements = wu.getValuesWithQualifiers( entity, hi.wd.stateOfUse,
		nil, {}, hi.wd.retrieved, nil, getLabelFromId, wikilang, fallbackLang )
	if #statements > 0 then
		lastEdit = hr.getLastedit( lastEdit, statements )
		for _, statement in ipairs( statements ) do
			tableInsert( result, statement.value )
		end
	end

	-- merging all statements
	local result = table.concat( result, hi.texts.delimiter )

	-- adding maintenance categories
	if result ~= '' then
		categIds.fromWikidata = ''
		if showOptions.addCategories then
			result = result .. hr.getCategories( formatStr )
		end
	end
	return result, lastEdit
end

-- invoke helper functions
-- splitting show parameters
local function splitAndCheck( s )
	local arr = {}
	local err = {}
	if isSet( s ) then
		for _, v in ipairs( mw.text.split( s, ',', true ) ) do
			v = mw.text.trim( v )
			if not hi.show[ v ] then
				table.insert( err, v )
			else
				arr[ v ] = ''
			end
		end
	end
	return arr, err
end

-- check if pareameters are valid
local function checkParameters( args )
	local err = {}
	if not args and type( args ) ~= 'table' then
		return err
	end
	for k, _ in pairs( args ) do
		if not hi.params[ k ] then
			table.insert( err, k )
		end
	end
	return err
end

-- formating and concating error strings
local function getErrorStr( arr, formatStr )
	return #arr == 0 and '' or
		mw.ustring.format( formatStr, table.concat( arr, ', ' ) )
end

-- main function for template #invoke calls
-- id: Q id of an establishment
-- format: output format like 'opened at %s'
-- fallback: fallback language if labels are not available in content language
function hr.getHours( frame )
    local args = frame.args
    args.id = mw.text.trim( args.id or '' )
    if not isSet( args.id ) or not mw.wikibase.isValidEntityId( args.id ) then
        return hi.categories.invalidId
    end
    if not isSet( args.format ) then
    	args.format = hi.texts.format
    else
	    local _, count = args.format:gsub( '%%s', '%%s' )
    	if count ~= 1 then
        	args.format = hi.texts.format
    	end
    end
    args.fallback = args.fallback or ''
    local wikilang = mw.getContentLanguage():getCode()

	local paramErr = checkParameters( args )
	local show, showErr = splitAndCheck( args.show )

    local result, _ = hr.getHoursFromWikidata( args.id, wikilang, args.fallback,
    	'', show, false, nil )
    if result ~= '' then
    	result = mw.ustring.format( args.format, result )
    end
    return result .. getErrorStr( paramErr, hi.categories.unknownParams )
    	.. getErrorStr( showErr, hi.categories.unknownShowOptions )
end

return hr