footballdata-api 0.3.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1 -1
- data/Manifest.txt +2 -3
- data/Rakefile +1 -3
- data/bin/fbdat +102 -48
- data/config/leagues_tier1.csv +44 -0
- data/lib/footballdata/convert-score.rb +44 -0
- data/lib/footballdata/convert.rb +58 -92
- data/lib/footballdata/leagues.rb +1 -1
- data/lib/footballdata/prettyprint.rb +6 -1
- data/lib/footballdata/version.rb +2 -2
- data/lib/footballdata.rb +2 -8
- metadata +5 -34
- data/config/leagues.csv +0 -54
- data/config/timezones.csv +0 -27
- data/lib/footballdata/timezones.rb +0 -97
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 97e75a5cbf4b952cb018a40c4ff790a201c26cbd95af1027bae3e7ac2153d1b5
|
4
|
+
data.tar.gz: a4d0cfd2e6ff636f030cd6c4d4d949f2173763cc39352b28b3425ad9aa077796
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d1c8650effbb80caec23410ee3fc739f379976359f044b29eff3a02d7bd82f23d76f7daffd6b2cfa76dfbdcbc6be53e178a04929b02401a30641f29cb3d0bd18
|
7
|
+
data.tar.gz: 39d810683288fc35703f19fd1643af8bb0b8332168ae4d4e819eb681444ea4c27e7c0b36daccf27fc97d02704aa6325696579a6d5b3941988cd6bef8f400d0ec
|
data/CHANGELOG.md
CHANGED
data/Manifest.txt
CHANGED
@@ -3,9 +3,9 @@ Manifest.txt
|
|
3
3
|
README.md
|
4
4
|
Rakefile
|
5
5
|
bin/fbdat
|
6
|
-
config/
|
7
|
-
config/timezones.csv
|
6
|
+
config/leagues_tier1.csv
|
8
7
|
lib/footballdata.rb
|
8
|
+
lib/footballdata/convert-score.rb
|
9
9
|
lib/footballdata/convert.rb
|
10
10
|
lib/footballdata/download.rb
|
11
11
|
lib/footballdata/leagues.rb
|
@@ -13,5 +13,4 @@ lib/footballdata/mods.rb
|
|
13
13
|
lib/footballdata/prettyprint.rb
|
14
14
|
lib/footballdata/stat.rb
|
15
15
|
lib/footballdata/teams.rb
|
16
|
-
lib/footballdata/timezones.rb
|
17
16
|
lib/footballdata/version.rb
|
data/Rakefile
CHANGED
@@ -18,10 +18,8 @@ Hoe.spec 'footballdata-api' do
|
|
18
18
|
self.history_file = 'CHANGELOG.md'
|
19
19
|
|
20
20
|
self.extra_deps = [
|
21
|
-
['
|
22
|
-
['season-formats'],
|
21
|
+
['football-timezones'],
|
23
22
|
['webget'],
|
24
|
-
['cocos'], ## later pull in with sportsdb-writers
|
25
23
|
]
|
26
24
|
|
27
25
|
self.licenses = ['Public Domain']
|
data/bin/fbdat
CHANGED
@@ -16,16 +16,13 @@ Webcache.root = if File.exist?( '/sports/cache' )
|
|
16
16
|
'./cache'
|
17
17
|
end
|
18
18
|
|
19
|
-
## note - free tier (tier one) plan - 10 requests/minute
|
20
|
-
## (one request every 6 seconds 6*10=60 secs)
|
21
|
-
## 10 API calls per minute max.
|
22
|
-
## note - default sleep (delay in secs) is 3 sec(s)
|
23
|
-
Webget.config.sleep = 10
|
24
|
-
|
25
|
-
|
26
|
-
Footballdata.config.convert.out_dir = '/sports/cache.api.fbdat' if File.exist?( '/sports/cache.api.fbdat' )
|
27
|
-
|
28
19
|
|
20
|
+
Footballdata.config.convert.out_dir = if File.exist?( '/sports/cache.api.fbdat' )
|
21
|
+
puts " setting convert out_dir to >/sports/cache.api.fbdat<"
|
22
|
+
'/sports/cache.api.fbdat'
|
23
|
+
else
|
24
|
+
'.' ## use working dir
|
25
|
+
end
|
29
26
|
|
30
27
|
|
31
28
|
require 'optparse'
|
@@ -38,6 +35,7 @@ def self.main( args=ARGV )
|
|
38
35
|
opts = {
|
39
36
|
cached: false,
|
40
37
|
convert: true,
|
38
|
+
file: nil,
|
41
39
|
}
|
42
40
|
|
43
41
|
parser = OptionParser.new do |parser|
|
@@ -48,9 +46,20 @@ parser = OptionParser.new do |parser|
|
|
48
46
|
opts[:cached] = cached
|
49
47
|
end
|
50
48
|
|
51
|
-
parser.on( "--no-convert",
|
52
|
-
"turn off conversion to .csv in #{Footballdata.config.convert.out_dir} - default is (#{
|
53
|
-
opts[:convert] =
|
49
|
+
parser.on( "--[no-]convert",
|
50
|
+
"turn on/off conversion to .csv in #{Footballdata.config.convert.out_dir} - default is (#{opts[:convert]})" ) do |convert|
|
51
|
+
opts[:convert] = convert
|
52
|
+
end
|
53
|
+
|
54
|
+
parser.on( "--print", "--pp",
|
55
|
+
"pretty print cached data in #{Webcache.root}; no download & conversion") do |print|
|
56
|
+
opts[:cached] = true
|
57
|
+
opts[:convert] = false
|
58
|
+
end
|
59
|
+
|
60
|
+
parser.on( "-f FILE", "--file FILE",
|
61
|
+
"read leagues (and seasons) via .csv file") do |file|
|
62
|
+
opts[:file] = file
|
54
63
|
end
|
55
64
|
end
|
56
65
|
parser.parse!( args )
|
@@ -63,6 +72,19 @@ puts "ARGV:"
|
|
63
72
|
p args
|
64
73
|
|
65
74
|
|
75
|
+
## note - free tier (tier one) plan - 10 requests/minute
|
76
|
+
## (one request every 6 seconds 6*10=60 secs)
|
77
|
+
## 10 API calls per minute max.
|
78
|
+
## note - default sleep (delay in secs) is 3 sec(s)
|
79
|
+
|
80
|
+
## change from 10 to 1 sec(s) for interactive use
|
81
|
+
## assume --file/-f as non-interactive/batch use for now
|
82
|
+
Webget.config.sleep = opts[:file] ? 10 : 1
|
83
|
+
|
84
|
+
|
85
|
+
|
86
|
+
|
87
|
+
|
66
88
|
## try special args
|
67
89
|
|
68
90
|
if ['plan', 'plans',
|
@@ -113,11 +135,13 @@ end
|
|
113
135
|
##
|
114
136
|
## todo - add more date offsets - t+2,t+3,t+4, etc.
|
115
137
|
|
138
|
+
|
139
|
+
|
116
140
|
date = if ['y', 'yesterday', 't-1', '-1'].include?( args[0] )
|
117
141
|
Date.today-1
|
118
142
|
elsif ['t', 'tomorrow', 't+1', '1', '+1'].include?( args[0] )
|
119
143
|
Date.today+1
|
120
|
-
elsif ['m', 'match', 'matches', 'today'].include?( args[0]
|
144
|
+
elsif ['m', 'match', 'matches', 'today'].include?( args[0] )
|
121
145
|
Date.today
|
122
146
|
else
|
123
147
|
nil
|
@@ -165,51 +189,81 @@ end
|
|
165
189
|
##
|
166
190
|
## note - only use "generic" uniform league codes for now!!
|
167
191
|
|
168
|
-
league_code = (args[0] || 'eng.1').downcase
|
169
|
-
|
170
|
-
## todo - find a better name
|
171
|
-
## use internal_league_code or such - why? why not?
|
172
|
-
### convenience helpers - lets you use eng.1, euro, etc.
|
173
|
-
## check if mapping for league_code
|
174
|
-
metal_league_code = find_league!( league_code )
|
175
192
|
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
193
|
+
datasets = if opts[:file]
|
194
|
+
read_datasets( opts[:file] )
|
195
|
+
else
|
196
|
+
parse_datasets_args( args )
|
197
|
+
end
|
198
|
+
|
199
|
+
|
200
|
+
## step 0 - validate and fill-up seasons etc.
|
201
|
+
datasets.each do |dataset|
|
202
|
+
league_key, seasons = dataset
|
203
|
+
|
204
|
+
## todo - find a better name
|
205
|
+
## use internal_league_code or such - why? why not?
|
206
|
+
### convenience helpers - lets you use eng.1, euro, etc.
|
207
|
+
## check if mapping for league_code
|
208
|
+
metal_league_code = find_league!( league_key )
|
209
|
+
|
210
|
+
## note - default to latest season of league
|
211
|
+
## might be 2024/25 or 2024 or
|
212
|
+
# for world cup 2022 or such
|
213
|
+
if seasons.empty?
|
214
|
+
seasons = case league_key
|
215
|
+
when 'world' then [Season('2022')]
|
216
|
+
when 'euro' then [Season('2024')]
|
217
|
+
when 'br.1', 'copa.l' then [Season('2024')]
|
218
|
+
else [Season('2024/25')]
|
219
|
+
end
|
220
|
+
dataset[1] = seasons
|
221
|
+
end
|
194
222
|
end
|
195
223
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
224
|
+
## step 1 - download
|
225
|
+
datasets.each do |league_key, seasons|
|
226
|
+
## todo - find a better name
|
227
|
+
## use internal_league_code or such - why? why not?
|
228
|
+
### convenience helpers - lets you use eng.1, euro, etc.
|
229
|
+
## check if mapping for league_code
|
230
|
+
metal_league_code = find_league!( league_key )
|
231
|
+
seasons.each do |season|
|
232
|
+
season_start_year = season.start_year ## use year - why? why not?
|
233
|
+
pp [metal_league_code, season_start_year]
|
234
|
+
|
235
|
+
if opts[:cached]
|
236
|
+
## do nothing
|
237
|
+
else
|
238
|
+
## download dataset(s)
|
239
|
+
## try download
|
240
|
+
## note: include teams (for convert) for now too!!
|
241
|
+
Metal.teams( metal_league_code, season_start_year )
|
242
|
+
Metal.matches( metal_league_code, season_start_year )
|
243
|
+
end
|
244
|
+
|
245
|
+
url = Metal.competition_matches_url( metal_league_code,
|
246
|
+
season_start_year )
|
247
|
+
pp url
|
248
|
+
#=> "http://api.football-data.org/v4/competitions/EC/matches?season=2024"
|
200
249
|
|
201
|
-
data = Webcache.read_json( url )
|
202
|
-
## pp data
|
250
|
+
data = Webcache.read_json( url )
|
251
|
+
## pp data
|
203
252
|
|
204
|
-
pp_matches( data )
|
253
|
+
pp_matches( data )
|
254
|
+
end # each season
|
255
|
+
end # each dataset
|
205
256
|
|
206
257
|
|
207
258
|
if opts[:convert]
|
208
|
-
|
209
|
-
|
259
|
+
puts "==> converting to .csv"
|
260
|
+
datasets.each do |league_key, seasons|
|
261
|
+
seasons.each do |season|
|
262
|
+
convert( league: league_key, season: season )
|
263
|
+
end
|
264
|
+
end
|
210
265
|
end
|
211
266
|
|
212
|
-
|
213
267
|
end # def self.main
|
214
268
|
end # module Footballdata
|
215
269
|
|
@@ -0,0 +1,44 @@
|
|
1
|
+
key, code, seasons
|
2
|
+
|
3
|
+
eng.1, PL, 2024/25 2023/24 2022/23 2021/22 2020/21
|
4
|
+
# incl. team(s) from wales
|
5
|
+
eng.2, ELC, 2024/25 2023/24 2022/23 2021/22 2020/21
|
6
|
+
# PL - Premier League, England 27 seasons | 2019-08-09 - 2020-07-25 / matchday 31
|
7
|
+
# ELC - Championship, England 3 seasons | 2019-08-02 - 2020-07-22 / matchday 38
|
8
|
+
|
9
|
+
es.1, PD, 2024/25 2023/24 2022/23 2021/22 2020/21
|
10
|
+
# PD - Primera Division, Spain 27 seasons | 2019-08-16 - 2020-07-19 / matchday 31
|
11
|
+
|
12
|
+
pt.1, PPL, 2024/25 2023/24 2022/23 2021/22 2020/21
|
13
|
+
# PPL - Primeira Liga, Portugal 9 seasons | 2019-08-10 - 2020-07-26 / matchday 28
|
14
|
+
|
15
|
+
de.1, BL1, 2024/25 2023/24 2022/23 2021/22 2020/21
|
16
|
+
# BL1 - Bundesliga, Germany 24 seasons | 2019-08-16 - 2020-06-27 / matchday 34
|
17
|
+
|
18
|
+
nl.1, DED, 2024/25 2023/24 2022/23 2021/22 2020/21
|
19
|
+
# DED - Eredivisie, Netherlands 10 seasons | 2019-08-09 - 2020-03-08 / matchday 34
|
20
|
+
|
21
|
+
fr.1, FL1, 2024/25 2023/24 2022/23 2021/22 2020/21
|
22
|
+
# incl. team(s) monaco
|
23
|
+
|
24
|
+
it.1, SA, 2024/25 2023/24 2022/23 2021/22 2020/21
|
25
|
+
# SA - Serie A, Italy 15 seasons | 2019-08-24 - 2020-08-02 / matchday 27
|
26
|
+
|
27
|
+
br.1, BSA, 2024 2023 2022 2021 2020
|
28
|
+
|
29
|
+
|
30
|
+
########
|
31
|
+
## int'l cups
|
32
|
+
uefa.cl, CL, 2024/25 2023/24 2022/23 2021/22 2020/21
|
33
|
+
## note: cl is country code for chile!! - use champs - why? why not?
|
34
|
+
## was europe.cl / cl
|
35
|
+
## todo/check: use champs and NOT cl - why? why not?
|
36
|
+
|
37
|
+
copa.l, CLI, 2024 2023 2022 2021
|
38
|
+
## Copa Libertadores
|
39
|
+
|
40
|
+
|
41
|
+
############
|
42
|
+
## national teams
|
43
|
+
euro, EC, 2024 2021
|
44
|
+
world, WC, 2022
|
@@ -0,0 +1,44 @@
|
|
1
|
+
|
2
|
+
module Footballdata
|
3
|
+
|
4
|
+
def self.convert_score( score )
|
5
|
+
## duration: REGULAR · PENALTY_SHOOTOUT · EXTRA_TIME
|
6
|
+
ft, ht, et, pen = ["","","",""]
|
7
|
+
|
8
|
+
if score['duration'] == 'REGULAR'
|
9
|
+
ft = "#{score['fullTime']['home']}-#{score['fullTime']['away']}"
|
10
|
+
ht = "#{score['halfTime']['home']}-#{score['halfTime']['away']}"
|
11
|
+
elsif score['duration'] == 'EXTRA_TIME'
|
12
|
+
et = "#{score['regularTime']['home']+score['extraTime']['home']}"
|
13
|
+
et << "-"
|
14
|
+
et << "#{score['regularTime']['away']+score['extraTime']['away']}"
|
15
|
+
|
16
|
+
ft = "#{score['regularTime']['home']}-#{score['regularTime']['away']}"
|
17
|
+
ht = "#{score['halfTime']['home']}-#{score['halfTime']['away']}"
|
18
|
+
elsif score['duration'] == 'PENALTY_SHOOTOUT'
|
19
|
+
if score['extraTime']
|
20
|
+
## quick & dirty hack - calc et via regulartime+extratime
|
21
|
+
pen = "#{score['penalties']['home']}-#{score['penalties']['away']}"
|
22
|
+
et = "#{score['regularTime']['home']+score['extraTime']['home']}"
|
23
|
+
et << "-"
|
24
|
+
et << "#{score['regularTime']['away']+score['extraTime']['away']}"
|
25
|
+
|
26
|
+
ft = "#{score['regularTime']['home']}-#{score['regularTime']['away']}"
|
27
|
+
ht = "#{score['halfTime']['home']}-#{score['halfTime']['away']}"
|
28
|
+
else ### south american-style (no extra time)
|
29
|
+
## quick & dirty hacke - calc ft via fullTime-penalties
|
30
|
+
pen = "#{score['penalties']['home']}-#{score['penalties']['away']}"
|
31
|
+
ft = "#{score['fullTime']['home']-score['penalties']['home']}"
|
32
|
+
ft << "-"
|
33
|
+
ft << "#{score['fullTime']['away']-score['penalties']['away']}"
|
34
|
+
ht = "#{score['halfTime']['home']}-#{score['halfTime']['away']}"
|
35
|
+
end
|
36
|
+
else
|
37
|
+
puts "!! unknown score duration:"
|
38
|
+
pp score
|
39
|
+
exit 1
|
40
|
+
end
|
41
|
+
|
42
|
+
[ft,ht,et,pen]
|
43
|
+
end
|
44
|
+
end # module Footballdata
|
data/lib/footballdata/convert.rb
CHANGED
@@ -1,63 +1,21 @@
|
|
1
1
|
|
2
2
|
module Footballdata
|
3
3
|
|
4
|
-
|
5
|
-
def self.convert_score( score )
|
6
|
-
## duration: REGULAR · PENALTY_SHOOTOUT · EXTRA_TIME
|
7
|
-
ft, ht, et, pen = ["","","",""]
|
8
|
-
|
9
|
-
if score['duration'] == 'REGULAR'
|
10
|
-
ft = "#{score['fullTime']['home']}-#{score['fullTime']['away']}"
|
11
|
-
ht = "#{score['halfTime']['home']}-#{score['halfTime']['away']}"
|
12
|
-
elsif score['duration'] == 'EXTRA_TIME'
|
13
|
-
et = "#{score['regularTime']['home']+score['extraTime']['home']}"
|
14
|
-
et << "-"
|
15
|
-
et << "#{score['regularTime']['away']+score['extraTime']['away']}"
|
16
|
-
|
17
|
-
ft = "#{score['regularTime']['home']}-#{score['regularTime']['away']}"
|
18
|
-
ht = "#{score['halfTime']['home']}-#{score['halfTime']['away']}"
|
19
|
-
elsif score['duration'] == 'PENALTY_SHOOTOUT'
|
20
|
-
if score['extraTime']
|
21
|
-
## quick & dirty hack - calc et via regulartime+extratime
|
22
|
-
pen = "#{score['penalties']['home']}-#{score['penalties']['away']}"
|
23
|
-
et = "#{score['regularTime']['home']+score['extraTime']['home']}"
|
24
|
-
et << "-"
|
25
|
-
et << "#{score['regularTime']['away']+score['extraTime']['away']}"
|
26
|
-
|
27
|
-
ft = "#{score['regularTime']['home']}-#{score['regularTime']['away']}"
|
28
|
-
ht = "#{score['halfTime']['home']}-#{score['halfTime']['away']}"
|
29
|
-
else ### south american-style (no extra time)
|
30
|
-
## quick & dirty hacke - calc ft via fullTime-penalties
|
31
|
-
pen = "#{score['penalties']['home']}-#{score['penalties']['away']}"
|
32
|
-
ft = "#{score['fullTime']['home']-score['penalties']['home']}"
|
33
|
-
ft << "-"
|
34
|
-
ft << "#{score['fullTime']['away']-score['penalties']['away']}"
|
35
|
-
ht = "#{score['halfTime']['home']}-#{score['halfTime']['away']}"
|
36
|
-
end
|
37
|
-
else
|
38
|
-
puts "!! unknown score duration:"
|
39
|
-
pp score
|
40
|
-
exit 1
|
41
|
-
end
|
42
|
-
|
43
|
-
[ft,ht,et,pen]
|
44
|
-
end
|
45
|
-
|
46
|
-
|
47
4
|
#######
|
48
5
|
## map round-like to higher-level stages
|
49
6
|
STAGES = {
|
50
7
|
'REGULAR_SEASON' => ['Regular'],
|
51
8
|
|
9
|
+
'QUALIFICATION' => ['Qualifying'],
|
52
10
|
'PRELIMINARY_ROUND' => ['Qualifying', 'Preliminary Round' ],
|
53
11
|
'PRELIMINARY_SEMI_FINALS' => ['Qualifying', 'Preliminary Semifinals' ],
|
54
12
|
'PRELIMINARY_FINAL' => ['Qualifying', 'Preliminary Final' ],
|
55
|
-
'1ST_QUALIFYING_ROUND' => ['Qualifying', '
|
56
|
-
'2ND_QUALIFYING_ROUND' => ['Qualifying', '
|
57
|
-
'3RD_QUALIFYING_ROUND' => ['Qualifying', '
|
58
|
-
'QUALIFICATION_ROUND_1' => ['Qualifying', '
|
59
|
-
'QUALIFICATION_ROUND_2' => ['Qualifying', '
|
60
|
-
'QUALIFICATION_ROUND_3' => ['Qualifying', '
|
13
|
+
'1ST_QUALIFYING_ROUND' => ['Qualifying', 'Round 1' ],
|
14
|
+
'2ND_QUALIFYING_ROUND' => ['Qualifying', 'Round 2' ],
|
15
|
+
'3RD_QUALIFYING_ROUND' => ['Qualifying', 'Round 3' ],
|
16
|
+
'QUALIFICATION_ROUND_1' => ['Qualifying', 'Round 1' ],
|
17
|
+
'QUALIFICATION_ROUND_2' => ['Qualifying', 'Round 2' ],
|
18
|
+
'QUALIFICATION_ROUND_3' => ['Qualifying', 'Round 3' ],
|
61
19
|
'ROUND_1' => ['Qualifying', 'Round 1'], ## use Qual. Round 1 - why? why not?
|
62
20
|
'ROUND_2' => ['Qualifying', 'Round 2'],
|
63
21
|
'ROUND_3' => ['Qualifying', 'Round 3'],
|
@@ -72,6 +30,7 @@ STAGES = {
|
|
72
30
|
'LAST_16' => ['Finals', 'Round of 16'], ## use Last 16 - why? why not?
|
73
31
|
'QUARTER_FINALS' => ['Finals', 'Quarterfinals'],
|
74
32
|
'SEMI_FINALS' => ['Finals', 'Semifinals'],
|
33
|
+
'THIRD_PLACE' => ['Finals', 'Third place play-off'],
|
75
34
|
'FINAL' => ['Finals', 'Final'],
|
76
35
|
}
|
77
36
|
|
@@ -80,13 +39,6 @@ STAGES = {
|
|
80
39
|
|
81
40
|
def self.convert( league:, season: )
|
82
41
|
|
83
|
-
### note/fix: cl (champions league for now is a "special" case)
|
84
|
-
# if league.downcase == 'cl'
|
85
|
-
# convert_cl( league: league,
|
86
|
-
# season: season )
|
87
|
-
# return
|
88
|
-
# end
|
89
|
-
|
90
42
|
season = Season( season ) ## cast (ensure) season class (NOT string, integer, etc.)
|
91
43
|
|
92
44
|
league_code = find_league!( league )
|
@@ -145,12 +97,40 @@ matches.each do |m|
|
|
145
97
|
score = m['score']
|
146
98
|
|
147
99
|
|
148
|
-
stage_key = m['stage']
|
149
100
|
|
101
|
+
group = m['group']
|
102
|
+
## GROUP_A
|
103
|
+
## shorten group to A|B|C etc.
|
104
|
+
if group && group =~ /^(GROUP_|Group )/
|
105
|
+
group = group.sub( /^(GROUP_|Group )/, '' )
|
106
|
+
else
|
107
|
+
if group.nil?
|
108
|
+
group = ''
|
109
|
+
else
|
110
|
+
puts "!! WARN - group defined with NON GROUP!? >#{group}< reset to empty"
|
111
|
+
puts " and matchday to >#{m['matchday']}<"
|
112
|
+
## reset group to empty
|
113
|
+
group = ''
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
stage_key = m['stage']
|
150
119
|
stats['stage'][ stage_key ] += 1 ## track stage counts
|
151
120
|
|
152
|
-
|
153
|
-
stage, stage_round
|
121
|
+
|
122
|
+
stage, stage_round = if group.empty?
|
123
|
+
## map stage to stage + round
|
124
|
+
STAGES[ stage_key ]
|
125
|
+
else
|
126
|
+
## if group defined ignore stage
|
127
|
+
## hard-core always to group for now
|
128
|
+
if stage_key != 'GROUP_STAGE'
|
129
|
+
puts "!! WARN - group defined BUT stage set to >#{stage_key}<"
|
130
|
+
puts " and matchday to >#{m['matchday']}<"
|
131
|
+
end
|
132
|
+
['Group', nil]
|
133
|
+
end
|
154
134
|
|
155
135
|
if stage.nil?
|
156
136
|
puts "!! ERROR - no stage mapping found for stage >#{stage_key}<"
|
@@ -165,23 +145,12 @@ matches.each do |m|
|
|
165
145
|
## matchday 1, 2 etc.
|
166
146
|
matchday = matchday_num.to_s
|
167
147
|
else
|
168
|
-
## note - if matchday defined
|
169
|
-
##
|
170
|
-
matchday =
|
171
|
-
matchday << stage_round
|
172
|
-
matchday << " | Leg #{matchday_num}" if matchday_num &&
|
173
|
-
(matchday_num == 1 || matchday_num == 2)
|
148
|
+
## note - if matchday/round defined, use it
|
149
|
+
## note - ignore possible leg in matchday for now
|
150
|
+
matchday = stage_round
|
174
151
|
end
|
175
152
|
|
176
153
|
|
177
|
-
|
178
|
-
group = m['group'] || ''
|
179
|
-
## GROUP_A
|
180
|
-
## shorten group to A|B|C etc.
|
181
|
-
group = group.sub( /^GROUP_/, '' )
|
182
|
-
|
183
|
-
|
184
|
-
|
185
154
|
teams[ team1 ] += 1
|
186
155
|
teams[ team2 ] += 1
|
187
156
|
|
@@ -214,7 +183,7 @@ matches.each do |m|
|
|
214
183
|
ht = ''
|
215
184
|
when 'FINISHED'
|
216
185
|
ft, ht, et, pen = convert_score( score )
|
217
|
-
when 'AWARDED'
|
186
|
+
when 'AWARDED' # AWARDED
|
218
187
|
assert( score['duration'] == 'REGULAR', 'score.duration REGULAR expected' )
|
219
188
|
ft = "#{score['fullTime']['home']}-#{score['fullTime']['away']}"
|
220
189
|
ft << ' (*)'
|
@@ -237,22 +206,16 @@ matches.each do |m|
|
|
237
206
|
end
|
238
207
|
|
239
208
|
|
240
|
-
|
241
|
-
## add time, timezone(tz)
|
242
|
-
## 2023-08-18T18:30:00Z
|
243
|
-
## e.g. "utcDate": "2020-05-09T00:00:00Z",
|
244
|
-
## "utcDate": "2023-08-18T18:30:00Z",
|
245
|
-
|
246
|
-
## -- todo - make sure / assert it's always utc - how???
|
247
|
-
## utc = ## tz_utc.strptime( m['utcDate'], '%Y-%m-%dT%H:%M:%SZ' )
|
248
|
-
## note: DateTime.strptime is supposed to be unaware of timezones!!!
|
249
|
-
## use to parse utc
|
209
|
+
|
250
210
|
utc = UTC.strptime( m['utcDate'], '%Y-%m-%dT%H:%M:%SZ' )
|
251
211
|
assert( utc.strftime( '%Y-%m-%dT%H:%M:%SZ' ) == m['utcDate'], 'utc time mismatch' )
|
252
212
|
|
253
213
|
|
214
|
+
## do NOT add time if status is SCHEDULED
|
215
|
+
## or POSTPONED for now
|
216
|
+
## otherwise assume time always present - why? why not?
|
217
|
+
##
|
254
218
|
## assume NOT valid utc time if 00:00
|
255
|
-
## do
|
256
219
|
if utc.hour == 0 && utc.min == 0 &&
|
257
220
|
['SCHEDULED','POSTPONED'].include?( m['status'] )
|
258
221
|
date = utc.strftime( '%Y-%m-%d' )
|
@@ -262,17 +225,20 @@ matches.each do |m|
|
|
262
225
|
local = tz.to_local( utc )
|
263
226
|
date = local.strftime( '%Y-%m-%d' )
|
264
227
|
time = local.strftime( '%H:%M' )
|
265
|
-
timezone = local.strftime( '%Z/%z' )
|
266
|
-
end
|
267
228
|
|
229
|
+
## pretty print timezone
|
230
|
+
### todo/fix - bundle into fmt_timezone method or such for reuse
|
231
|
+
tz_abbr = local.strftime( '%Z' ) ## e.g. EEST or if not available +03 or such
|
232
|
+
tz_offset = local.strftime( '%z' ) ## e.g. +0300
|
268
233
|
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
234
|
+
timezone = if tz_abbr =~ /^[+-][0-9]+$/ ## only digits (no abbrev.)
|
235
|
+
tz_offset
|
236
|
+
else
|
237
|
+
"#{tz_abbr}/#{tz_offset}"
|
238
|
+
end
|
239
|
+
end
|
273
240
|
|
274
241
|
|
275
|
-
## todo/fix: assert matchday is a number e.g. 1,2,3, etc.!!!
|
276
242
|
recs << [stage,
|
277
243
|
group,
|
278
244
|
matchday,
|
data/lib/footballdata/leagues.rb
CHANGED
@@ -2,7 +2,7 @@ module Footballdata
|
|
2
2
|
|
3
3
|
def self.find_league!( league )
|
4
4
|
@leagues ||= begin
|
5
|
-
recs = read_csv( "#{FootballdataApi.root}/config/
|
5
|
+
recs = read_csv( "#{FootballdataApi.root}/config/leagues_tier1.csv" )
|
6
6
|
leagues = {}
|
7
7
|
recs.each do |rec|
|
8
8
|
leagues[ rec['key'] ] = rec['code']
|
@@ -39,6 +39,7 @@ def self.fmt_match( rec )
|
|
39
39
|
TIMED
|
40
40
|
FINISHED
|
41
41
|
POSTPONED
|
42
|
+
AWARDED
|
42
43
|
IN_PLAY
|
43
44
|
].include?( status ), "unknown status - #{status}" )
|
44
45
|
|
@@ -89,7 +90,11 @@ def self.fmt_match( rec )
|
|
89
90
|
score << "#{et} a.e.t. "
|
90
91
|
score << "(#{ft}, #{ht})"
|
91
92
|
else
|
92
|
-
|
93
|
+
if !ht.empty?
|
94
|
+
score << "#{ft} (#{ht})"
|
95
|
+
else
|
96
|
+
score << "#{ft}"
|
97
|
+
end
|
93
98
|
end
|
94
99
|
|
95
100
|
buf << score
|
data/lib/footballdata/version.rb
CHANGED
data/lib/footballdata.rb
CHANGED
@@ -1,14 +1,8 @@
|
|
1
1
|
## 3rd party (our own)
|
2
|
-
require '
|
2
|
+
require 'football/timezones' # note - pulls in season/formats, cocos & tzinfo
|
3
3
|
require 'webget' ## incl. webget, webcache, webclient, etc.
|
4
4
|
|
5
5
|
|
6
|
-
require 'cocos' ## check if webget incl. cocos ??
|
7
|
-
|
8
|
-
|
9
|
-
require 'tzinfo'
|
10
|
-
|
11
|
-
|
12
6
|
module Footballdata
|
13
7
|
class Configuration
|
14
8
|
#########
|
@@ -36,13 +30,13 @@ end # module Footballdata
|
|
36
30
|
# our own code
|
37
31
|
require_relative 'footballdata/version'
|
38
32
|
require_relative 'footballdata/leagues'
|
39
|
-
require_relative 'footballdata/timezones'
|
40
33
|
|
41
34
|
require_relative 'footballdata/download'
|
42
35
|
require_relative 'footballdata/prettyprint'
|
43
36
|
|
44
37
|
require_relative 'footballdata/mods'
|
45
38
|
require_relative 'footballdata/convert'
|
39
|
+
require_relative 'footballdata/convert-score'
|
46
40
|
require_relative 'footballdata/teams'
|
47
41
|
|
48
42
|
|
metadata
CHANGED
@@ -1,31 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: footballdata-api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gerald Bauer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-09-
|
11
|
+
date: 2024-09-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - ">="
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '0'
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - ">="
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: season-formats
|
14
|
+
name: football-timezones
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
30
16
|
requirements:
|
31
17
|
- - ">="
|
@@ -52,20 +38,6 @@ dependencies:
|
|
52
38
|
- - ">="
|
53
39
|
- !ruby/object:Gem::Version
|
54
40
|
version: '0'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: cocos
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - ">="
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
62
|
-
type: :runtime
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - ">="
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '0'
|
69
41
|
- !ruby/object:Gem::Dependency
|
70
42
|
name: rdoc
|
71
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -116,9 +88,9 @@ files:
|
|
116
88
|
- README.md
|
117
89
|
- Rakefile
|
118
90
|
- bin/fbdat
|
119
|
-
- config/
|
120
|
-
- config/timezones.csv
|
91
|
+
- config/leagues_tier1.csv
|
121
92
|
- lib/footballdata.rb
|
93
|
+
- lib/footballdata/convert-score.rb
|
122
94
|
- lib/footballdata/convert.rb
|
123
95
|
- lib/footballdata/download.rb
|
124
96
|
- lib/footballdata/leagues.rb
|
@@ -126,7 +98,6 @@ files:
|
|
126
98
|
- lib/footballdata/prettyprint.rb
|
127
99
|
- lib/footballdata/stat.rb
|
128
100
|
- lib/footballdata/teams.rb
|
129
|
-
- lib/footballdata/timezones.rb
|
130
101
|
- lib/footballdata/version.rb
|
131
102
|
homepage: https://github.com/sportdb/sport.db
|
132
103
|
licenses:
|
data/config/leagues.csv
DELETED
@@ -1,54 +0,0 @@
|
|
1
|
-
key, code
|
2
|
-
|
3
|
-
eng.1, PL # incl. team(s) from wales
|
4
|
-
eng.2, ELC
|
5
|
-
# PL - Premier League, England 27 seasons | 2019-08-09 - 2020-07-25 / matchday 31
|
6
|
-
# ELC - Championship, England 3 seasons | 2019-08-02 - 2020-07-22 / matchday 38
|
7
|
-
#
|
8
|
-
# 2019 => 2019/20
|
9
|
-
# 2018 => 2018/19
|
10
|
-
# 2017 => xxx 2017-18 - requires subscription !!!
|
11
|
-
|
12
|
-
es.1, PD
|
13
|
-
# PD - Primera Division, Spain 27 seasons | 2019-08-16 - 2020-07-19 / matchday 31
|
14
|
-
|
15
|
-
pt.1, PPL
|
16
|
-
# PPL - Primeira Liga, Portugal 9 seasons | 2019-08-10 - 2020-07-26 / matchday 28
|
17
|
-
|
18
|
-
de.1, BL1
|
19
|
-
# BL1 - Bundesliga, Germany 24 seasons | 2019-08-16 - 2020-06-27 / matchday 34
|
20
|
-
|
21
|
-
nl.1, DED
|
22
|
-
# DED - Eredivisie, Netherlands 10 seasons | 2019-08-09 - 2020-03-08 / matchday 34
|
23
|
-
|
24
|
-
fr.1, FL1 # incl. team(s) monaco
|
25
|
-
# FL1 - Ligue 1, France
|
26
|
-
# 9 seasons | 2019-08-09 - 2020-05-31 / matchday 38
|
27
|
-
#
|
28
|
-
# 2019 => 2019/20
|
29
|
-
# 2018 => 2018/19
|
30
|
-
# 2017 => xxx 2017-18 - requires subscription !!!
|
31
|
-
|
32
|
-
it.1, SA
|
33
|
-
# SA - Serie A, Italy 15 seasons | 2019-08-24 - 2020-08-02 / matchday 27
|
34
|
-
|
35
|
-
br.1, BSA
|
36
|
-
# BSA - Série A, Brazil
|
37
|
-
# 4 seasons | 2020-05-03 - 2020-12-06 / matchday 10
|
38
|
-
#
|
39
|
-
# 2020 => 2020
|
40
|
-
# 2019 => 2019
|
41
|
-
# 2018 => 2018
|
42
|
-
# 2017 => xxx 2017 - requires subscription !!!
|
43
|
-
|
44
|
-
uefa.cl, CL ## note: cl is country code for chile!! - use champs - why? why not?
|
45
|
-
## was europe.cl / cl
|
46
|
-
## todo/check: use champs and NOT cl - why? why not?
|
47
|
-
|
48
|
-
copa.l, CLI
|
49
|
-
## Copa Libertadores
|
50
|
-
|
51
|
-
############
|
52
|
-
## national teams
|
53
|
-
euro, EC
|
54
|
-
world, WC
|
data/config/timezones.csv
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
key, zone
|
2
|
-
eng, Europe/London
|
3
|
-
es, Europe/Madrid
|
4
|
-
de, Europe/Berlin
|
5
|
-
fr, Europe/Paris
|
6
|
-
it, Europe/Rome
|
7
|
-
|
8
|
-
nl, Europe/Amsterdam
|
9
|
-
pt, Europe/Lisbon
|
10
|
-
|
11
|
-
## for champs default for not to cet (central european time) - why? why not?
|
12
|
-
uefa.cl, Europe/Paris
|
13
|
-
euro, Europe/Paris
|
14
|
-
|
15
|
-
## todo/fix - pt.1
|
16
|
-
## one team in madeira!!! check for different timezone??
|
17
|
-
## CD Nacional da Madeira
|
18
|
-
|
19
|
-
br, America/Sao_Paulo
|
20
|
-
## todo/fix - brazil has 4 timezones
|
21
|
-
## really only two in use for clubs
|
22
|
-
## west and east (amazonas et al)
|
23
|
-
## for now use west for all - why? why not?
|
24
|
-
copa.l, America/Sao_Paulo
|
25
|
-
|
26
|
-
|
27
|
-
## world+2022, add quatar here
|
@@ -1,97 +0,0 @@
|
|
1
|
-
|
2
|
-
class UTC
|
3
|
-
def self.now() Time.now.utc; end
|
4
|
-
def self.today() now.to_date; end
|
5
|
-
|
6
|
-
## -- todo - make sure / assert it's always utc - how???
|
7
|
-
## utc = ## tz_utc.strptime( m['utcDate'], '%Y-%m-%dT%H:%M:%SZ' )
|
8
|
-
## note: DateTime.strptime is supposed to be unaware of timezones!!!
|
9
|
-
## use to parse utc
|
10
|
-
## quick hack -
|
11
|
-
## use to_time.getutc instead of utc ???
|
12
|
-
def self.strptime( str, format )
|
13
|
-
DateTime.strptime( str, format ).to_time.utc
|
14
|
-
end
|
15
|
-
|
16
|
-
def self.find_zone( name )
|
17
|
-
zone = TZInfo::Timezone.get( name )
|
18
|
-
## wrap tzinfo timezone in our own - for adding more (auto)checks etc.
|
19
|
-
zone ? Timezone.new( zone ) : nil
|
20
|
-
end
|
21
|
-
|
22
|
-
class Timezone ## nested inside UTC
|
23
|
-
## todo/fix
|
24
|
-
## cache timezone - why? why not?
|
25
|
-
def initialize( zone )
|
26
|
-
@zone = zone
|
27
|
-
end
|
28
|
-
|
29
|
-
def to_local( time )
|
30
|
-
## assert time is Time (not Date or DateTIme)
|
31
|
-
## and assert utc!!!
|
32
|
-
assert( time.is_a?( Time ), "time #{time} is NOT of class Time; got #{time.class.name}" )
|
33
|
-
assert( time.utc?, "time #{time} is NOT utc; utc? returns #{time.utc?}" )
|
34
|
-
local = @zone.to_local( time )
|
35
|
-
local
|
36
|
-
end
|
37
|
-
|
38
|
-
def assert( cond, msg )
|
39
|
-
if cond
|
40
|
-
# do nothing
|
41
|
-
else
|
42
|
-
puts "!!! assert failed - #{msg}"
|
43
|
-
exit 1
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end # class Timezone
|
47
|
-
end # class UTC
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
module Footballdata
|
52
|
-
def self.find_zone!( league:, season: )
|
53
|
-
@zones ||= begin
|
54
|
-
recs = read_csv( "#{FootballdataApi.root}/config/timezones.csv" )
|
55
|
-
zones = {}
|
56
|
-
recs.each do |rec|
|
57
|
-
zone = UTC.find_zone( rec['zone'] )
|
58
|
-
if zone.nil?
|
59
|
-
## raise ArgumentError - invalid zone
|
60
|
-
puts "!! ERROR - cannot find timezone in timezone db:"
|
61
|
-
pp rec
|
62
|
-
exit 1
|
63
|
-
end
|
64
|
-
zones[ rec['key']] = zone
|
65
|
-
end
|
66
|
-
zones
|
67
|
-
end
|
68
|
-
|
69
|
-
|
70
|
-
## lookup first try by league+season
|
71
|
-
league_code = league.downcase
|
72
|
-
season = Season( season )
|
73
|
-
|
74
|
-
## e.g. world+2022, etc.
|
75
|
-
key = "#{league_code}+#{season}"
|
76
|
-
zone = @zones[key]
|
77
|
-
|
78
|
-
## try league e.g. eng.1 etc.
|
79
|
-
zone = @zones[league_code] if zone.nil?
|
80
|
-
|
81
|
-
## try first code only (country code )
|
82
|
-
if zone.nil?
|
83
|
-
code, _ = league_code.split( '.', 2 )
|
84
|
-
zone = @zones[code]
|
85
|
-
end
|
86
|
-
|
87
|
-
if zone.nil? ## still not found; report error
|
88
|
-
puts "!! ERROR: no timezone found for #{league} #{season}"
|
89
|
-
exit 1
|
90
|
-
end
|
91
|
-
|
92
|
-
zone
|
93
|
-
end
|
94
|
-
end # module Footballdata
|
95
|
-
|
96
|
-
|
97
|
-
|