footballdata-api 0.3.1 → 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
|