Modul:Hours

Aus Wikivoyage
Zur Navigation springen Zur Suche springen
Template-info.png Dokumentation für das Modul Hours[Ansicht] [Bearbeiten] [Versionsgeschichte] [Aktualisieren]

Anwendung

Das Modul liefert die Öffnungszeiten einer Einrichtung aus Wikidata. Es wird üblicherweise im Modul vCard verwendet, kann aber auch über einen {{#invoke: Hours | getHours }}-Aufruf in Vorlagen eingesetzt werden.

Versionsbezeichnung auf WikiData: 2020-10-31 Ok!

Verwendung in anderen Modulen

Dieses Modul ist notwendig für die Ausführung folgender Module. Bei Anpassungen sollte die Funktionstüchtigkeit der folgenden Module geprüft werden. Benutze dazu auch diese Tracking-Kategorie um Fehler zu finden, die sich dann auf Artikel auswirken:

Benötigte weitere Module

Dieses Modul benötigt folgende weitere Module: Hours/i18n • Wikidata utilities

Beispiele

id Label Öffnungszeiten
Q99477686 Chuchichäschtli Geöffnet: täglich 17:00–24:00
Q6373 British Museum Geöffnet: täglich 10:00–17:00; geschlossen: 24. Dez., 25. Dez., 26. Dez., 1. Jan.
Q219867 Bahnhof King’s Cross Geöffnet: Mo–Fr 5:00–1:36; Sa 5:00–1:11; So 5:30–1:36
Q2292227 Skigebiet Gaißau-Hintersee Geöffnet: täglich 9:00–16:00 (15. Dez.–15. Mär.)
Q11323320 Pablo Gargallo Museum Geöffnet: Di–Sa 10:00–14:00, 17:00–21:00; So, Feiertag 10:00–14:30
Q27831262 Öömrang Hüs Geöffnet: Mo–Fr 11:00–13:30 (Hochsaison, Nebensaison); geschlossen: Sa–So, Feiertag
Q99690036 Nationalpark-Tor Rurberg Geöffnet: täglich 9:00–13:00, 13:30–17:00 (1. Apr.–31. Okt.), 10:00–13:00, 13:30–16:00 (1. Nov.–31. Mär.)
Q2583681 Grand Egyptian Museum Geöffnet: im Bau befindlich

Wartungskategorien

Anmerkungen

Bisher sind nur bei wenigen Wikidata-Einträgen Öffnungszeiten hinterlegt, so dass es sicher im Laufe der Zeit zu Anpassungen kommen wird.

  • Bisher werden alle Schließtage gesammelt und geschlossen am Ende ausgegeben, also nicht hinter jedem einzelnen Datensatz (erkennbar am trennenden Semikolon),
  • Evtl. beim Nutzungszustand (P5817) Angaben wie „für die Öffentlichkeit zugänglich“ nicht ausgeben, weil die Öffnung erwartet wird.
  • Beobachtung der Nutzung der einzelnen Eigenschaften, insbesondere P5102 und neu erfasste Eigenschaften. Evtl. Eigenschaften einschränken.

Mögliche Parameter für {{#invoke: Hours | getHours }}

  • Erforderliche Parameter:
    • |id= Wikidata-Id für die entsprechende Einrichtung mit vorangestelltem Q wie z. B. Q12345.
  • Optionale Parameter:
    • |format= Ausgabeformat, das anstelle der Standardzeichenkette verwendet werden soll, wenn eine Öffnungszeit bestimmt wurde, wie z. B. „Geöffnet: %s“. %s ist der Platzhalter für die Öffnungszeit.
    • |fallback= Ersatzsprache, auf die zurückgegriffen werden soll, wenn ein Öffnungszeit-Label nicht in der Inhaltssprache des Wikis vorliegt. Z. B. en.
    • |show= Kommaseparierte Liste von Anzeigeoptionen, die die Standardwerte ersetzen. Gegenwärtig werden die beiden Werte msg (Anzeige der Wartungskategorien) und nomsg (keine Anzeige der Wartungskategorien) unterstützt.

Beispiele

  • {{#invoke: Hours | getHours | id = Q6373 }}
  • {{#invoke: Hours | getHours | id = Q6373 | fallback = en }}
  • {{#invoke: Hours | getHours | id = Q6373 | show = msg }}
Hinweise
-- getting opening hours from Wikidata

-- documentation
local Hours = {
	suite  = 'Hours',
	serial = '2020-10-31',
	item   = 99600452
}

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

-- module variable
local hr = {}

-- 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 getTime( 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 = getTime( 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, v in pairs( categIds ) do
		result = result .. ( hi.categories[ k ] or hi.categories.unknownError )
	end
	if showOptions.demo then
		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

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

	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

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

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

	-- converting statements to human-readable strings
	local result = {}
	local s
	for i, statement in ipairs( statements ) do
		-- opening times
		local times = {}
		local count = math.max( getCount( statement[ hi.wd.hourOpen ] ),
			getCount( statement[ hi.wd.hourClosed ] ) )
		local is24_7 = getLabelFromTables( hi.times.is24_7 )
		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
		local 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

	-- adding separated close statements (P3026), qualifiers (P5102, P5817, ...)
	local statements = wu.getValuesWithQualifiers( entity, hi.wd.closed,
		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
			local 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 ) )

	-- adding additional statements
	local statements = wu.getValuesWithQualifiers( entity, hi.wd.stateOfUse,
		{}, 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
local function splitAndCheck( s )
	local err = {}
	if not isSet( s ) then
		return {}, err
	end
	local arr = {}
	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
	return arr, err
end

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

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

-- module administration
function hr.Hours()
	return Hours
end

function hr.failsafe( version )
	return wu._failsafe( version, Hours ) or ''
end

return hr