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 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
-