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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b3350fb47d596e6d5b2e4d73582b2615020d56db38cf86d9545a10b1760494c1
4
- data.tar.gz: d7ba6c51f3e12beed59df7b851e8d533a838de67e73c5b1d025ab7a1256caa7d
3
+ metadata.gz: 97e75a5cbf4b952cb018a40c4ff790a201c26cbd95af1027bae3e7ac2153d1b5
4
+ data.tar.gz: a4d0cfd2e6ff636f030cd6c4d4d949f2173763cc39352b28b3425ad9aa077796
5
5
  SHA512:
6
- metadata.gz: 2943bcf6f2da49dfc1cab26da0933957d2bde2fb13749c7ce4d4896214484bac3edcea197e01ed9b2b63f92e2e74f560ac49189f63be7644522b7b24b7cbed73
7
- data.tar.gz: 945cc2d0cb9346434c6f3875c33dd153867318b73e067c5574bc6e789f7b3b293d5c7f126c80221c676998eaa0e532647b7a25b960d082097209b6a1428f879e
6
+ metadata.gz: d1c8650effbb80caec23410ee3fc739f379976359f044b29eff3a02d7bd82f23d76f7daffd6b2cfa76dfbdcbc6be53e178a04929b02401a30641f29cb3d0bd18
7
+ data.tar.gz: 39d810683288fc35703f19fd1643af8bb0b8332168ae4d4e819eb681444ea4c27e7c0b36daccf27fc97d02704aa6325696579a6d5b3941988cd6bef8f400d0ec
data/CHANGELOG.md CHANGED
@@ -1,4 +1,4 @@
1
- ### 0.3.1
1
+ ### 0.4.2
2
2
 
3
3
  ### 0.0.1 / 2024-07-03
4
4
 
data/Manifest.txt CHANGED
@@ -3,9 +3,9 @@ Manifest.txt
3
3
  README.md
4
4
  Rakefile
5
5
  bin/fbdat
6
- config/leagues.csv
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
- ['tzinfo'],
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 (#{!opts[:convert]})" ) do |convert|
53
- opts[:convert] = !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] || 'today' ) ## make default - why? why not?
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
- season = Season( args[1] ||
178
- (['euro',
179
- 'copa.l',
180
- 'br.1'].include?(league_code) ? '2024' : '2024/25'))
181
-
182
- season_start_year = season.start_year ## use year - why? why not?
183
-
184
- pp [metal_league_code, season_start_year]
185
-
186
- if opts[:cached]
187
- ## do nothing
188
- else
189
- ## download dataset(s)
190
- ## try download
191
- ## note: include teams (for convert) for now too!!
192
- Metal.teams( metal_league_code, season_start_year )
193
- Metal.matches( metal_league_code, season_start_year )
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
- url = Metal.competition_matches_url( metal_league_code,
197
- season_start_year )
198
- pp url
199
- #=> "http://api.football-data.org/v4/competitions/EC/matches?season=2024"
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
- puts "==> converting to .csv"
209
- convert( league: league_code, season: season )
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
@@ -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', 'Qual. Round 1' ],
56
- '2ND_QUALIFYING_ROUND' => ['Qualifying', 'Qual. Round 2' ],
57
- '3RD_QUALIFYING_ROUND' => ['Qualifying', 'Qual. Round 3' ],
58
- 'QUALIFICATION_ROUND_1' => ['Qualifying', 'Qual. Round 1' ],
59
- 'QUALIFICATION_ROUND_2' => ['Qualifying', 'Qual. Round 2' ],
60
- 'QUALIFICATION_ROUND_3' => ['Qualifying', 'Qual. Round 3' ],
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
- ## map stage to stage + round
153
- stage, stage_round = STAGES[ stage_key ]
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; assume leg e.g. 1|2
169
- ## skip if different than one or two for now
170
- matchday = String.new
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
- ## do NOT add time if status is SCHEDULED
270
- ## or POSTPONED for now
271
- ## otherwise assume time always present - why? why not?
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,
@@ -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/leagues.csv" )
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
- score << "#{ft} (#{ht})"
93
+ if !ht.empty?
94
+ score << "#{ft} (#{ht})"
95
+ else
96
+ score << "#{ft}"
97
+ end
93
98
  end
94
99
 
95
100
  buf << score
@@ -1,8 +1,8 @@
1
1
 
2
2
  module FootballdataApi
3
3
  MAJOR = 0 ## todo: namespace inside version or something - why? why not??
4
- MINOR = 3
5
- PATCH = 1
4
+ MINOR = 4
5
+ PATCH = 2
6
6
  VERSION = [MAJOR,MINOR,PATCH].join('.')
7
7
 
8
8
  def self.version
data/lib/footballdata.rb CHANGED
@@ -1,14 +1,8 @@
1
1
  ## 3rd party (our own)
2
- require 'season/formats' ## add season support
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.3.1
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-10 00:00:00.000000000 Z
11
+ date: 2024-09-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: tzinfo
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/leagues.csv
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
-