fitzroy 1.7.1 → 1.7.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.
package/dist/cli.js CHANGED
@@ -106,13 +106,21 @@ var init_result = __esm({
106
106
  });
107
107
 
108
108
  // src/lib/date-utils.ts
109
- function parseFootyWireDate(dateStr, defaultYear) {
110
- const trimmed = dateStr.trim();
111
- if (trimmed === "") {
112
- return null;
109
+ function parseDate(raw, defaultYear) {
110
+ if (typeof raw === "number") {
111
+ const date = new Date(raw * 1e3);
112
+ return Number.isNaN(date.getTime()) ? null : date;
113
+ }
114
+ const trimmed = raw.trim();
115
+ if (trimmed === "") return null;
116
+ if (/^\d{4}-\d{2}-\d{2}/.test(trimmed)) {
117
+ const stripped = trimmed.replace(/[Zz]$|[+-]\d{2}:\d{2}$/, "");
118
+ const utc = stripped.includes("T") ? `${stripped}Z` : `${stripped}T00:00:00Z`;
119
+ const date = new Date(utc);
120
+ return Number.isNaN(date.getTime()) ? null : date;
113
121
  }
114
122
  const withoutDow = trimmed.replace(/^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\w*\s+/i, "");
115
- const normalised = withoutDow.replace(/-/g, " ");
123
+ const normalised = withoutDow.replace(/[-/]/g, " ");
116
124
  const fullMatch = /^(\d{1,2})\s+([A-Za-z]+)\s+(\d{4})$/.exec(normalised);
117
125
  if (fullMatch) {
118
126
  const [, dayStr, monthStr, yearStr] = fullMatch;
@@ -120,6 +128,13 @@ function parseFootyWireDate(dateStr, defaultYear) {
120
128
  return buildUtcDate(Number.parseInt(yearStr, 10), monthStr, Number.parseInt(dayStr, 10));
121
129
  }
122
130
  }
131
+ const mdyMatch = /^([A-Za-z]+)\s+(\d{1,2})\s+(\d{4})$/.exec(normalised);
132
+ if (mdyMatch) {
133
+ const [, monthStr, dayStr, yearStr] = mdyMatch;
134
+ if (dayStr && monthStr && yearStr) {
135
+ return buildUtcDate(Number.parseInt(yearStr, 10), monthStr, Number.parseInt(dayStr, 10));
136
+ }
137
+ }
123
138
  const shortMatch = /^(\d{1,2})\s+([A-Za-z]+)(?:\s+(\d{1,2}):(\d{2})(am|pm))?$/i.exec(normalised);
124
139
  if (shortMatch && defaultYear != null) {
125
140
  const [, dayStr, monthStr, hourStr, minStr, ampm] = shortMatch;
@@ -127,40 +142,15 @@ function parseFootyWireDate(dateStr, defaultYear) {
127
142
  const monthIndex = MONTH_ABBREV_TO_INDEX.get(monthStr.toLowerCase());
128
143
  if (monthIndex === void 0) return null;
129
144
  const day = Number.parseInt(dayStr, 10);
130
- const hasTime = hourStr && minStr && ampm;
131
- if (!hasTime) {
145
+ if (!hourStr || !minStr || !ampm) {
132
146
  return buildUtcDate(defaultYear, monthStr, day);
133
147
  }
134
- let aestHours = Number.parseInt(hourStr, 10);
148
+ let hours = Number.parseInt(hourStr, 10);
135
149
  const minutes = Number.parseInt(minStr, 10);
136
- if (ampm.toLowerCase() === "pm" && aestHours < 12) aestHours += 12;
137
- if (ampm.toLowerCase() === "am" && aestHours === 12) aestHours = 0;
138
- const date = melbourneLocalToUtc(defaultYear, monthIndex, day, aestHours, minutes);
139
- if (Number.isNaN(date.getTime())) return null;
140
- return date;
141
- }
142
- return null;
143
- }
144
- function parseAflTablesDate(dateStr) {
145
- const trimmed = dateStr.trim();
146
- if (trimmed === "") {
147
- return null;
148
- }
149
- const withoutDow = trimmed.replace(/^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\w*\s+/i, "");
150
- const normalised = withoutDow.replace(/[-/]/g, " ");
151
- const dmy = /^(\d{1,2})\s+([A-Za-z]+)\s+(\d{4})$/.exec(normalised);
152
- if (dmy) {
153
- const [, dayStr, monthStr, yearStr] = dmy;
154
- if (dayStr && monthStr && yearStr) {
155
- return buildUtcDate(Number.parseInt(yearStr, 10), monthStr, Number.parseInt(dayStr, 10));
156
- }
157
- }
158
- const mdy = /^([A-Za-z]+)\s+(\d{1,2})\s+(\d{4})$/.exec(normalised);
159
- if (mdy) {
160
- const [, monthStr, dayStr, yearStr] = mdy;
161
- if (dayStr && monthStr && yearStr) {
162
- return buildUtcDate(Number.parseInt(yearStr, 10), monthStr, Number.parseInt(dayStr, 10));
163
- }
150
+ if (ampm.toLowerCase() === "pm" && hours < 12) hours += 12;
151
+ if (ampm.toLowerCase() === "am" && hours === 12) hours = 0;
152
+ const date = melbourneLocalToUtc(defaultYear, monthIndex, day, hours, minutes);
153
+ return Number.isNaN(date.getTime()) ? null : date;
164
154
  }
165
155
  return null;
166
156
  }
@@ -716,7 +706,7 @@ function transformMatchItems(items, season, competition, source = "afl-api") {
716
706
  roundNumber: item.round?.roundNumber ?? 0,
717
707
  roundType: inferRoundType(item.round?.name ?? ""),
718
708
  roundName: item.round?.name ?? null,
719
- date: new Date(item.match.utcStartTime),
709
+ date: parseDate(item.match.utcStartTime) ?? new Date(item.match.utcStartTime),
720
710
  venue: item.venue?.name ? normaliseVenueName(item.venue.name) : "",
721
711
  homeTeam: normaliseTeamName(item.match.homeTeam.name),
722
712
  awayTeam: normaliseTeamName(item.match.awayTeam.name),
@@ -755,6 +745,7 @@ var FINALS_PATTERN, ROUND_CODE_MAP, ROUND_NUMBER_PATTERN;
755
745
  var init_match_results = __esm({
756
746
  "src/transforms/match-results.ts"() {
757
747
  "use strict";
748
+ init_date_utils();
758
749
  init_team_mapping();
759
750
  init_venue_mapping();
760
751
  FINALS_PATTERN = /final|elimination|qualifying|preliminary|semi|grand/i;
@@ -815,7 +806,7 @@ function parseMatchList(html, year) {
815
806
  const scoreLink = scoreCell.find("a").attr("href") ?? "";
816
807
  const midMatch = /mid=(\d+)/.exec(scoreLink);
817
808
  const matchId = midMatch?.[1] ? `FW_${midMatch[1]}` : `FW_${year}_R${currentRound}_${homeTeam}`;
818
- const date = parseFootyWireDate(dateText, year) ?? new Date(Date.UTC(year, 0, 1));
809
+ const date = parseDate(dateText, year) ?? new Date(Date.UTC(year, 0, 1));
819
810
  const homeGoals = Math.floor(homePoints / 6);
820
811
  const homeBehinds = homePoints - homeGoals * 6;
821
812
  const awayGoals = Math.floor(awayPoints / 6);
@@ -895,7 +886,7 @@ function parseFixtureList(html, year) {
895
886
  if (teamLinks.length < 2) return;
896
887
  const homeTeam = normaliseTeamName($(teamLinks[0]).text().trim());
897
888
  const awayTeam = normaliseTeamName($(teamLinks[1]).text().trim());
898
- const date = parseFootyWireDate(dateText, year) ?? new Date(Date.UTC(year, 0, 1));
889
+ const date = parseDate(dateText, year) ?? new Date(Date.UTC(year, 0, 1));
899
890
  gameNumber++;
900
891
  const scoreCell = cells.length >= 5 ? $(cells[4]) : null;
901
892
  const scoreText = scoreCell?.text().trim() ?? "";
@@ -985,7 +976,7 @@ function teamNameToFootyWireSlug(teamName) {
985
976
  }
986
977
  function normaliseDob(raw) {
987
978
  if (!raw) return null;
988
- const parsed = parseFootyWireDate(raw);
979
+ const parsed = parseDate(raw);
989
980
  if (parsed) return parsed.toISOString().slice(0, 10);
990
981
  return raw;
991
982
  }
@@ -2379,7 +2370,7 @@ function transformSquiggleGamesToResults(games, season) {
2379
2370
  roundNumber: g.round,
2380
2371
  roundType: inferRoundType(g.roundname),
2381
2372
  roundName: g.roundname || null,
2382
- date: new Date(g.unixtime * 1e3),
2373
+ date: parseDate(g.unixtime) ?? new Date(g.unixtime * 1e3),
2383
2374
  venue: normaliseVenueName(g.venue),
2384
2375
  homeTeam: normaliseTeamName(g.hteam),
2385
2376
  awayTeam: normaliseTeamName(g.ateam),
@@ -2419,7 +2410,7 @@ function transformSquiggleGamesToFixture(games, season) {
2419
2410
  season,
2420
2411
  roundNumber: g.round,
2421
2412
  roundType: inferRoundType(g.roundname),
2422
- date: new Date(g.unixtime * 1e3),
2413
+ date: parseDate(g.unixtime) ?? new Date(g.unixtime * 1e3),
2423
2414
  venue: normaliseVenueName(g.venue),
2424
2415
  homeTeam: normaliseTeamName(g.hteam),
2425
2416
  awayTeam: normaliseTeamName(g.ateam),
@@ -2445,6 +2436,7 @@ function transformSquiggleStandings(standings) {
2445
2436
  var init_squiggle2 = __esm({
2446
2437
  "src/transforms/squiggle.ts"() {
2447
2438
  "use strict";
2439
+ init_date_utils();
2448
2440
  init_team_mapping();
2449
2441
  init_venue_mapping();
2450
2442
  init_match_results();
@@ -2458,7 +2450,7 @@ function toFixture(item, season, fallbackRoundNumber, competition) {
2458
2450
  season,
2459
2451
  roundNumber: item.round?.roundNumber ?? fallbackRoundNumber,
2460
2452
  roundType: inferRoundType(item.round?.name ?? ""),
2461
- date: new Date(item.match.utcStartTime),
2453
+ date: parseDate(item.match.utcStartTime) ?? new Date(item.match.utcStartTime),
2462
2454
  venue: normaliseVenueName(item.venue?.name ?? ""),
2463
2455
  homeTeam: normaliseTeamName(item.match.homeTeam.name),
2464
2456
  awayTeam: normaliseTeamName(item.match.awayTeam.name),
@@ -2525,6 +2517,7 @@ var init_fixture = __esm({
2525
2517
  "src/api/fixture.ts"() {
2526
2518
  "use strict";
2527
2519
  init_concurrency();
2520
+ init_date_utils();
2528
2521
  init_errors();
2529
2522
  init_result();
2530
2523
  init_team_mapping();
@@ -2776,9 +2769,9 @@ function parseQuarterScores(text) {
2776
2769
  function parseDateFromInfo(text, year) {
2777
2770
  const dateMatch = /(\d{1,2}-[A-Z][a-z]{2}-\d{4})/.exec(text);
2778
2771
  if (dateMatch?.[1]) {
2779
- return parseAflTablesDate(dateMatch[1]) ?? new Date(year, 0, 1);
2772
+ return parseDate(dateMatch[1]) ?? new Date(year, 0, 1);
2780
2773
  }
2781
- return parseAflTablesDate(text) ?? new Date(year, 0, 1);
2774
+ return parseDate(text) ?? new Date(year, 0, 1);
2782
2775
  }
2783
2776
  function parseVenueFromInfo(html) {
2784
2777
  const $ = cheerio5.load(html);
@@ -3805,7 +3798,7 @@ function mapRow(i, c, isAflw, competition) {
3805
3798
  roundNumber: roundAt(c.round, i),
3806
3799
  team: normaliseTeamName(team),
3807
3800
  competition,
3808
- date: dateStr ? new Date(dateStr) : null,
3801
+ date: dateStr ? parseDate(dateStr) : null,
3809
3802
  homeTeam: homeTeam ? normaliseTeamName(homeTeam) : null,
3810
3803
  awayTeam: awayTeam ? normaliseTeamName(awayTeam) : null,
3811
3804
  playerId: String(c.playerId?.[i] ?? ""),
@@ -3887,6 +3880,7 @@ var REQUIRED_COLUMN_GROUPS, AFLM_COLUMNS, AFLW_COLUMNS;
3887
3880
  var init_fryzigg_player_stats = __esm({
3888
3881
  "src/transforms/fryzigg-player-stats.ts"() {
3889
3882
  "use strict";
3883
+ init_date_utils();
3890
3884
  init_errors();
3891
3885
  init_result();
3892
3886
  init_team_mapping();
@@ -4108,7 +4102,7 @@ async function fetchPlayerStats(query) {
4108
4102
  competition,
4109
4103
  source: "afl-api",
4110
4104
  teamIdMap,
4111
- date: new Date(item.match.utcStartTime),
4105
+ date: parseDate(item.match.utcStartTime) ?? new Date(item.match.utcStartTime),
4112
4106
  homeTeam: normaliseTeamName(item.match.homeTeam.name),
4113
4107
  awayTeam: normaliseTeamName(item.match.awayTeam.name)
4114
4108
  })
@@ -4171,6 +4165,7 @@ var init_player_stats2 = __esm({
4171
4165
  "src/api/player-stats.ts"() {
4172
4166
  "use strict";
4173
4167
  init_concurrency();
4168
+ init_date_utils();
4174
4169
  init_errors();
4175
4170
  init_result();
4176
4171
  init_team_mapping();
@@ -4297,7 +4292,7 @@ async function fetchSquad(query) {
4297
4292
  displayName: `${p.player.firstName} ${p.player.surname}`,
4298
4293
  jumperNumber: p.jumperNumber ?? null,
4299
4294
  position: p.position ?? null,
4300
- dateOfBirth: p.player.dateOfBirth ? new Date(p.player.dateOfBirth) : null,
4295
+ dateOfBirth: p.player.dateOfBirth ? parseDate(p.player.dateOfBirth) : null,
4301
4296
  heightCm: p.player.heightInCm || null,
4302
4297
  weightKg: p.player.weightInKg || null,
4303
4298
  draftYear: p.player.draftYear ? Number.parseInt(p.player.draftYear, 10) || null : null,
@@ -4317,6 +4312,7 @@ async function fetchSquad(query) {
4317
4312
  var init_teams = __esm({
4318
4313
  "src/api/teams.ts"() {
4319
4314
  "use strict";
4315
+ init_date_utils();
4320
4316
  init_errors();
4321
4317
  init_result();
4322
4318
  init_team_mapping();
@@ -5800,7 +5796,7 @@ resolveAliases();
5800
5796
  var main = defineCommand11({
5801
5797
  meta: {
5802
5798
  name: "fitzroy",
5803
- version: "1.7.1",
5799
+ version: "1.7.2",
5804
5800
  description: "TypeScript port of the fitzRoy R package \u2014 fetch AFL data from the command line"
5805
5801
  },
5806
5802
  subCommands: {
package/dist/index.d.ts CHANGED
@@ -598,65 +598,32 @@ declare function fetchSquad2(query: SquadQuery): Promise<Result<Squad, Error>>;
598
598
  * @module
599
599
  */
600
600
  /**
601
- * Parse a UTC ISO 8601 string from the AFL API into a Date.
601
+ * Parse any AFL date string or timestamp into a correct UTC Date.
602
602
  *
603
- * The AFL API returns dates like `"2024-03-14T06:20:00.000Z"` or
604
- * `"2024-03-14T06:20:00Z"`.
603
+ * Accepts every format seen across AFL data sources and always returns
604
+ * a proper UTC Date. Input format is auto-detected:
605
605
  *
606
- * @param iso - A UTC ISO 8601 date string
607
- * @returns A Date object, or null if parsing fails
606
+ * | Input | Source | Handling |
607
+ * |---|---|---|
608
+ * | `1709622000` (number) | Squiggle unix timestamp | × 1000 → UTC |
609
+ * | `"2026-03-05T08:30:00"` | AFL API `utcStartTime` (no Z) | Force UTC |
610
+ * | `"2026-03-05T08:30:00.000Z"` | AFL API (with Z) | Parse as UTC |
611
+ * | `"Thu 13 Mar 7:30pm"` | FootyWire (Melbourne local) | AEST/AEDT → UTC |
612
+ * | `"16-Mar-2024"` / `"16 Mar 2024"` | AFL Tables / FootyWire | Midnight UTC |
613
+ * | `"Sat 16 Mar 2024"` | FootyWire (day-of-week prefix) | Midnight UTC |
608
614
  *
609
- * @example
610
- * ```ts
611
- * parseAflApiDate("2024-03-14T06:20:00.000Z")
612
- * // => Date(2024-03-14T06:20:00.000Z)
613
- * ```
615
+ * @param raw - A date string or unix timestamp (seconds)
616
+ * @param defaultYear - Year to use when the string lacks one (FootyWire fixtures)
617
+ * @returns A Date object in UTC, or null if parsing fails
614
618
  */
619
+ declare function parseDate(raw: string | number, defaultYear?: number): Date | null;
620
+ /** @deprecated Use {@link parseDate} instead. */
615
621
  declare function parseAflApiDate(iso: string): Date | null;
616
- /**
617
- * Parse a date string from FootyWire into a Date.
618
- *
619
- * FootyWire uses formats like:
620
- * - `"Sat 16 Mar 2024"` (day-of-week, day, month-abbrev, year)
621
- * - `"16 Mar 2024"` (day, month-abbrev, year)
622
- * - `"16-Mar-2024"` (day-month-year with hyphens)
623
- * - `"Thu 13 Mar 7:30pm"` (day-of-week, day, month, time — no year)
624
- * - `"13 Mar"` (day, month — no year)
625
- *
626
- * @param dateStr - A FootyWire date string
627
- * @param defaultYear - Year to use when the string lacks one (e.g. fixture pages)
628
- * @returns A Date object (UTC), or null if parsing fails
629
- *
630
- * @example
631
- * ```ts
632
- * parseFootyWireDate("Sat 16 Mar 2024")
633
- * // => Date(2024-03-16T00:00:00.000Z)
634
- *
635
- * parseFootyWireDate("Thu 13 Mar 7:30pm", 2025)
636
- * // => Date(2025-03-13T09:30:00.000Z) — time stored as AEST offset from UTC
637
- * ```
638
- */
622
+ /** @deprecated Use {@link parseDate} instead. */
623
+ declare function parseAflApiMatchTime(iso: string): Date | null;
624
+ /** @deprecated Use {@link parseDate} instead. */
639
625
  declare function parseFootyWireDate(dateStr: string, defaultYear?: number): Date | null;
640
- /**
641
- * Parse a date string from AFL Tables into a Date.
642
- *
643
- * AFL Tables uses formats like:
644
- * - `"16-Mar-2024"` (DD-Mon-YYYY)
645
- * - `"Sat 16-Mar-2024"` (Dow DD-Mon-YYYY)
646
- * - `"16 Mar 2024"` (DD Mon YYYY)
647
- *
648
- * For very old historical matches, dates may be partial (e.g. just a year),
649
- * which are not supported and return null.
650
- *
651
- * @param dateStr - An AFL Tables date string
652
- * @returns A Date object (midnight UTC), or null if parsing fails
653
- *
654
- * @example
655
- * ```ts
656
- * parseAflTablesDate("16-Mar-2024")
657
- * // => Date(2024-03-16T00:00:00.000Z)
658
- * ```
659
- */
626
+ /** @deprecated Use {@link parseDate} instead. */
660
627
  declare function parseAflTablesDate(dateStr: string): Date | null;
661
628
  /**
662
629
  * Format a Date as an AEST/AEDT-aware display string.
@@ -666,12 +633,6 @@ declare function parseAflTablesDate(dateStr: string): Date | null;
666
633
  *
667
634
  * @param date - The Date to format
668
635
  * @returns A formatted string like `"Thu 14 Mar 2024 5:20 PM AEDT"`
669
- *
670
- * @example
671
- * ```ts
672
- * toAestString(new Date("2024-03-14T06:20:00.000Z"))
673
- * // => "Thu 14 Mar 2024 5:20 PM AEDT"
674
- * ```
675
636
  */
676
637
  declare function toAestString(date: Date): string;
677
638
  /**
@@ -2299,4 +2260,4 @@ declare function transformSquiggleGamesToFixture(games: readonly SquiggleGame[],
2299
2260
  * Transform Squiggle standings into LadderEntry objects.
2300
2261
  */
2301
2262
  declare function transformSquiggleStandings(standings: readonly SquiggleStanding[]): LadderEntry[];
2302
- export { transformSquiggleStandings, transformSquiggleGamesToResults, transformSquiggleGamesToFixture, transformPlayerStats, transformMatchRoster, transformMatchItems, transformLadderEntries, transformFryziggPlayerStats, toAestString, parseFootyWireDate, parseAflTablesDate, parseAflApiDate, ok, normaliseVenueName, normaliseTeamName, inferRoundType, fetchTeams2 as fetchTeams, fetchTeamStats2 as fetchTeamStats, fetchSquad2 as fetchSquad, fetchPlayerStats2 as fetchPlayerStats, fetchPlayerDetails, fetchMatchResults, fetchLineup, fetchLadder2 as fetchLadder, fetchFixture, fetchCoachesVotes, fetchAwards, err, computeLadder, ValidationError, UnsupportedSourceError, TransformContext, TeamStatsSummaryType, TeamStatsQuery, TeamStatsEntry, TeamScoreSchema, TeamScore, TeamQuery, TeamPlayersSchema, TeamPlayers, TeamListSchema, TeamList, TeamItemSchema, TeamItem, Team, SquiggleStandingsResponseSchema, SquiggleStandingsResponse, SquiggleStandingSchema, SquiggleStanding, SquiggleGamesResponseSchema, SquiggleGamesResponse, SquiggleGameSchema, SquiggleGame, SquiggleClientOptions, SquiggleClient, SquadSchema, SquadQuery, SquadPlayerItemSchema, SquadPlayerItem, SquadPlayerInnerSchema, SquadPlayer, SquadListSchema, SquadList, Squad, SeasonRoundQuery, ScrapeError, ScoreSchema, Score, RoundType, RoundSchema, RoundListSchema, RoundList, Round, RosterPlayerSchema, RosterPlayer, RisingStarNomination, Result, QuarterScore, PlayerStatsQuery, PlayerStatsListSchema, PlayerStatsList, PlayerStatsItemSchema, PlayerStatsItem, PlayerStats, PlayerGameStatsSchema, PlayerGameStats, PlayerDetailsQuery, PlayerDetails, PeriodScoreSchema, PeriodScore, Ok, MatchStatus, MatchRosterSchema, MatchRoster, MatchResult, MatchQuery, MatchItemSchema, MatchItemListSchema, MatchItemList, MatchItem, LineupQuery, LineupPlayer, Lineup, LadderResponseSchema, LadderResponse, LadderQuery, LadderEntryRawSchema, LadderEntryRaw, LadderEntry, Ladder, FryziggTransformOptions, FryziggClientOptions, FryziggClient, FootyWireClientOptions, FootyWireClient, Fixture, Err, DataSource, CompseasonSchema, CompseasonListSchema, CompseasonList, Compseason, CompetitionSchema, CompetitionListSchema, CompetitionList, CompetitionCode, Competition, CoachesVoteQuery, CoachesVote, CfsVenueSchema, CfsVenue, CfsScoreSchema, CfsScore, CfsMatchTeamSchema, CfsMatchTeam, CfsMatchSchema, CfsMatch, BrownlowVote, AwardType, AwardQuery, Award, AllAustralianSelection, AflTablesClientOptions, AflTablesClient, AflCoachesClientOptions, AflCoachesClient, AflApiTokenSchema, AflApiToken, AflApiError, AflApiClientOptions, AflApiClient };
2263
+ export { transformSquiggleStandings, transformSquiggleGamesToResults, transformSquiggleGamesToFixture, transformPlayerStats, transformMatchRoster, transformMatchItems, transformLadderEntries, transformFryziggPlayerStats, toAestString, parseFootyWireDate, parseDate, parseAflTablesDate, parseAflApiMatchTime, parseAflApiDate, ok, normaliseVenueName, normaliseTeamName, inferRoundType, fetchTeams2 as fetchTeams, fetchTeamStats2 as fetchTeamStats, fetchSquad2 as fetchSquad, fetchPlayerStats2 as fetchPlayerStats, fetchPlayerDetails, fetchMatchResults, fetchLineup, fetchLadder2 as fetchLadder, fetchFixture, fetchCoachesVotes, fetchAwards, err, computeLadder, ValidationError, UnsupportedSourceError, TransformContext, TeamStatsSummaryType, TeamStatsQuery, TeamStatsEntry, TeamScoreSchema, TeamScore, TeamQuery, TeamPlayersSchema, TeamPlayers, TeamListSchema, TeamList, TeamItemSchema, TeamItem, Team, SquiggleStandingsResponseSchema, SquiggleStandingsResponse, SquiggleStandingSchema, SquiggleStanding, SquiggleGamesResponseSchema, SquiggleGamesResponse, SquiggleGameSchema, SquiggleGame, SquiggleClientOptions, SquiggleClient, SquadSchema, SquadQuery, SquadPlayerItemSchema, SquadPlayerItem, SquadPlayerInnerSchema, SquadPlayer, SquadListSchema, SquadList, Squad, SeasonRoundQuery, ScrapeError, ScoreSchema, Score, RoundType, RoundSchema, RoundListSchema, RoundList, Round, RosterPlayerSchema, RosterPlayer, RisingStarNomination, Result, QuarterScore, PlayerStatsQuery, PlayerStatsListSchema, PlayerStatsList, PlayerStatsItemSchema, PlayerStatsItem, PlayerStats, PlayerGameStatsSchema, PlayerGameStats, PlayerDetailsQuery, PlayerDetails, PeriodScoreSchema, PeriodScore, Ok, MatchStatus, MatchRosterSchema, MatchRoster, MatchResult, MatchQuery, MatchItemSchema, MatchItemListSchema, MatchItemList, MatchItem, LineupQuery, LineupPlayer, Lineup, LadderResponseSchema, LadderResponse, LadderQuery, LadderEntryRawSchema, LadderEntryRaw, LadderEntry, Ladder, FryziggTransformOptions, FryziggClientOptions, FryziggClient, FootyWireClientOptions, FootyWireClient, Fixture, Err, DataSource, CompseasonSchema, CompseasonListSchema, CompseasonList, Compseason, CompetitionSchema, CompetitionListSchema, CompetitionList, CompetitionCode, Competition, CoachesVoteQuery, CoachesVote, CfsVenueSchema, CfsVenue, CfsScoreSchema, CfsScore, CfsMatchTeamSchema, CfsMatchTeam, CfsMatchSchema, CfsMatch, BrownlowVote, AwardType, AwardQuery, Award, AllAustralianSelection, AflTablesClientOptions, AflTablesClient, AflCoachesClientOptions, AflCoachesClient, AflApiTokenSchema, AflApiToken, AflApiError, AflApiClientOptions, AflApiClient };
package/dist/index.js CHANGED
@@ -46,20 +46,21 @@ function err(error) {
46
46
  import * as cheerio2 from "cheerio";
47
47
 
48
48
  // src/lib/date-utils.ts
49
- function parseAflApiDate(iso) {
50
- const date = new Date(iso);
51
- if (Number.isNaN(date.getTime())) {
52
- return null;
49
+ function parseDate(raw, defaultYear) {
50
+ if (typeof raw === "number") {
51
+ const date = new Date(raw * 1e3);
52
+ return Number.isNaN(date.getTime()) ? null : date;
53
53
  }
54
- return date;
55
- }
56
- function parseFootyWireDate(dateStr, defaultYear) {
57
- const trimmed = dateStr.trim();
58
- if (trimmed === "") {
59
- return null;
54
+ const trimmed = raw.trim();
55
+ if (trimmed === "") return null;
56
+ if (/^\d{4}-\d{2}-\d{2}/.test(trimmed)) {
57
+ const stripped = trimmed.replace(/[Zz]$|[+-]\d{2}:\d{2}$/, "");
58
+ const utc = stripped.includes("T") ? `${stripped}Z` : `${stripped}T00:00:00Z`;
59
+ const date = new Date(utc);
60
+ return Number.isNaN(date.getTime()) ? null : date;
60
61
  }
61
62
  const withoutDow = trimmed.replace(/^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\w*\s+/i, "");
62
- const normalised = withoutDow.replace(/-/g, " ");
63
+ const normalised = withoutDow.replace(/[-/]/g, " ");
63
64
  const fullMatch = /^(\d{1,2})\s+([A-Za-z]+)\s+(\d{4})$/.exec(normalised);
64
65
  if (fullMatch) {
65
66
  const [, dayStr, monthStr, yearStr] = fullMatch;
@@ -67,6 +68,13 @@ function parseFootyWireDate(dateStr, defaultYear) {
67
68
  return buildUtcDate(Number.parseInt(yearStr, 10), monthStr, Number.parseInt(dayStr, 10));
68
69
  }
69
70
  }
71
+ const mdyMatch = /^([A-Za-z]+)\s+(\d{1,2})\s+(\d{4})$/.exec(normalised);
72
+ if (mdyMatch) {
73
+ const [, monthStr, dayStr, yearStr] = mdyMatch;
74
+ if (dayStr && monthStr && yearStr) {
75
+ return buildUtcDate(Number.parseInt(yearStr, 10), monthStr, Number.parseInt(dayStr, 10));
76
+ }
77
+ }
70
78
  const shortMatch = /^(\d{1,2})\s+([A-Za-z]+)(?:\s+(\d{1,2}):(\d{2})(am|pm))?$/i.exec(normalised);
71
79
  if (shortMatch && defaultYear != null) {
72
80
  const [, dayStr, monthStr, hourStr, minStr, ampm] = shortMatch;
@@ -74,42 +82,29 @@ function parseFootyWireDate(dateStr, defaultYear) {
74
82
  const monthIndex = MONTH_ABBREV_TO_INDEX.get(monthStr.toLowerCase());
75
83
  if (monthIndex === void 0) return null;
76
84
  const day = Number.parseInt(dayStr, 10);
77
- const hasTime = hourStr && minStr && ampm;
78
- if (!hasTime) {
85
+ if (!hourStr || !minStr || !ampm) {
79
86
  return buildUtcDate(defaultYear, monthStr, day);
80
87
  }
81
- let aestHours = Number.parseInt(hourStr, 10);
88
+ let hours = Number.parseInt(hourStr, 10);
82
89
  const minutes = Number.parseInt(minStr, 10);
83
- if (ampm.toLowerCase() === "pm" && aestHours < 12) aestHours += 12;
84
- if (ampm.toLowerCase() === "am" && aestHours === 12) aestHours = 0;
85
- const date = melbourneLocalToUtc(defaultYear, monthIndex, day, aestHours, minutes);
86
- if (Number.isNaN(date.getTime())) return null;
87
- return date;
90
+ if (ampm.toLowerCase() === "pm" && hours < 12) hours += 12;
91
+ if (ampm.toLowerCase() === "am" && hours === 12) hours = 0;
92
+ const date = melbourneLocalToUtc(defaultYear, monthIndex, day, hours, minutes);
93
+ return Number.isNaN(date.getTime()) ? null : date;
88
94
  }
89
95
  return null;
90
96
  }
97
+ function parseAflApiDate(iso) {
98
+ return parseDate(iso);
99
+ }
100
+ function parseAflApiMatchTime(iso) {
101
+ return parseDate(iso);
102
+ }
103
+ function parseFootyWireDate(dateStr, defaultYear) {
104
+ return parseDate(dateStr, defaultYear);
105
+ }
91
106
  function parseAflTablesDate(dateStr) {
92
- const trimmed = dateStr.trim();
93
- if (trimmed === "") {
94
- return null;
95
- }
96
- const withoutDow = trimmed.replace(/^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\w*\s+/i, "");
97
- const normalised = withoutDow.replace(/[-/]/g, " ");
98
- const dmy = /^(\d{1,2})\s+([A-Za-z]+)\s+(\d{4})$/.exec(normalised);
99
- if (dmy) {
100
- const [, dayStr, monthStr, yearStr] = dmy;
101
- if (dayStr && monthStr && yearStr) {
102
- return buildUtcDate(Number.parseInt(yearStr, 10), monthStr, Number.parseInt(dayStr, 10));
103
- }
104
- }
105
- const mdy = /^([A-Za-z]+)\s+(\d{1,2})\s+(\d{4})$/.exec(normalised);
106
- if (mdy) {
107
- const [, monthStr, dayStr, yearStr] = mdy;
108
- if (dayStr && monthStr && yearStr) {
109
- return buildUtcDate(Number.parseInt(yearStr, 10), monthStr, Number.parseInt(dayStr, 10));
110
- }
111
- }
112
- return null;
107
+ return parseDate(dateStr);
113
108
  }
114
109
  function toAestString(date) {
115
110
  const formatter = new Intl.DateTimeFormat("en-AU", {
@@ -657,7 +652,7 @@ function transformMatchItems(items, season, competition, source = "afl-api") {
657
652
  roundNumber: item.round?.roundNumber ?? 0,
658
653
  roundType: inferRoundType(item.round?.name ?? ""),
659
654
  roundName: item.round?.name ?? null,
660
- date: new Date(item.match.utcStartTime),
655
+ date: parseDate(item.match.utcStartTime) ?? new Date(item.match.utcStartTime),
661
656
  venue: item.venue?.name ? normaliseVenueName(item.venue.name) : "",
662
657
  homeTeam: normaliseTeamName(item.match.homeTeam.name),
663
658
  awayTeam: normaliseTeamName(item.match.awayTeam.name),
@@ -972,7 +967,7 @@ function parseMatchList(html, year) {
972
967
  const scoreLink = scoreCell.find("a").attr("href") ?? "";
973
968
  const midMatch = /mid=(\d+)/.exec(scoreLink);
974
969
  const matchId = midMatch?.[1] ? `FW_${midMatch[1]}` : `FW_${year}_R${currentRound}_${homeTeam}`;
975
- const date = parseFootyWireDate(dateText, year) ?? new Date(Date.UTC(year, 0, 1));
970
+ const date = parseDate(dateText, year) ?? new Date(Date.UTC(year, 0, 1));
976
971
  const homeGoals = Math.floor(homePoints / 6);
977
972
  const homeBehinds = homePoints - homeGoals * 6;
978
973
  const awayGoals = Math.floor(awayPoints / 6);
@@ -1052,7 +1047,7 @@ function parseFixtureList(html, year) {
1052
1047
  if (teamLinks.length < 2) return;
1053
1048
  const homeTeam = normaliseTeamName($(teamLinks[0]).text().trim());
1054
1049
  const awayTeam = normaliseTeamName($(teamLinks[1]).text().trim());
1055
- const date = parseFootyWireDate(dateText, year) ?? new Date(Date.UTC(year, 0, 1));
1050
+ const date = parseDate(dateText, year) ?? new Date(Date.UTC(year, 0, 1));
1056
1051
  gameNumber++;
1057
1052
  const scoreCell = cells.length >= 5 ? $(cells[4]) : null;
1058
1053
  const scoreText = scoreCell?.text().trim() ?? "";
@@ -1162,7 +1157,7 @@ function teamNameToFootyWireSlug(teamName) {
1162
1157
  }
1163
1158
  function normaliseDob(raw) {
1164
1159
  if (!raw) return null;
1165
- const parsed = parseFootyWireDate(raw);
1160
+ const parsed = parseDate(raw);
1166
1161
  if (parsed) return parsed.toISOString().slice(0, 10);
1167
1162
  return raw;
1168
1163
  }
@@ -2399,7 +2394,7 @@ function transformSquiggleGamesToResults(games, season) {
2399
2394
  roundNumber: g.round,
2400
2395
  roundType: inferRoundType(g.roundname),
2401
2396
  roundName: g.roundname || null,
2402
- date: new Date(g.unixtime * 1e3),
2397
+ date: parseDate(g.unixtime) ?? new Date(g.unixtime * 1e3),
2403
2398
  venue: normaliseVenueName(g.venue),
2404
2399
  homeTeam: normaliseTeamName(g.hteam),
2405
2400
  awayTeam: normaliseTeamName(g.ateam),
@@ -2439,7 +2434,7 @@ function transformSquiggleGamesToFixture(games, season) {
2439
2434
  season,
2440
2435
  roundNumber: g.round,
2441
2436
  roundType: inferRoundType(g.roundname),
2442
- date: new Date(g.unixtime * 1e3),
2437
+ date: parseDate(g.unixtime) ?? new Date(g.unixtime * 1e3),
2443
2438
  venue: normaliseVenueName(g.venue),
2444
2439
  homeTeam: normaliseTeamName(g.hteam),
2445
2440
  awayTeam: normaliseTeamName(g.ateam),
@@ -2470,7 +2465,7 @@ function toFixture(item, season, fallbackRoundNumber, competition) {
2470
2465
  season,
2471
2466
  roundNumber: item.round?.roundNumber ?? fallbackRoundNumber,
2472
2467
  roundType: inferRoundType(item.round?.name ?? ""),
2473
- date: new Date(item.match.utcStartTime),
2468
+ date: parseDate(item.match.utcStartTime) ?? new Date(item.match.utcStartTime),
2474
2469
  venue: normaliseVenueName(item.venue?.name ?? ""),
2475
2470
  homeTeam: normaliseTeamName(item.match.homeTeam.name),
2476
2471
  awayTeam: normaliseTeamName(item.match.awayTeam.name),
@@ -2940,9 +2935,9 @@ function parseQuarterScores(text) {
2940
2935
  function parseDateFromInfo(text, year) {
2941
2936
  const dateMatch = /(\d{1,2}-[A-Z][a-z]{2}-\d{4})/.exec(text);
2942
2937
  if (dateMatch?.[1]) {
2943
- return parseAflTablesDate(dateMatch[1]) ?? new Date(year, 0, 1);
2938
+ return parseDate(dateMatch[1]) ?? new Date(year, 0, 1);
2944
2939
  }
2945
- return parseAflTablesDate(text) ?? new Date(year, 0, 1);
2940
+ return parseDate(text) ?? new Date(year, 0, 1);
2946
2941
  }
2947
2942
  function parseVenueFromInfo(html) {
2948
2943
  const $ = cheerio6.load(html);
@@ -3761,7 +3756,7 @@ function mapRow(i, c, isAflw, competition) {
3761
3756
  roundNumber: roundAt(c.round, i),
3762
3757
  team: normaliseTeamName(team),
3763
3758
  competition,
3764
- date: dateStr ? new Date(dateStr) : null,
3759
+ date: dateStr ? parseDate(dateStr) : null,
3765
3760
  homeTeam: homeTeam ? normaliseTeamName(homeTeam) : null,
3766
3761
  awayTeam: awayTeam ? normaliseTeamName(awayTeam) : null,
3767
3762
  playerId: String(c.playerId?.[i] ?? ""),
@@ -3997,7 +3992,7 @@ async function fetchPlayerStats(query) {
3997
3992
  competition,
3998
3993
  source: "afl-api",
3999
3994
  teamIdMap,
4000
- date: new Date(item.match.utcStartTime),
3995
+ date: parseDate(item.match.utcStartTime) ?? new Date(item.match.utcStartTime),
4001
3996
  homeTeam: normaliseTeamName(item.match.homeTeam.name),
4002
3997
  awayTeam: normaliseTeamName(item.match.awayTeam.name)
4003
3998
  })
@@ -4161,7 +4156,7 @@ async function fetchSquad(query) {
4161
4156
  displayName: `${p.player.firstName} ${p.player.surname}`,
4162
4157
  jumperNumber: p.jumperNumber ?? null,
4163
4158
  position: p.position ?? null,
4164
- dateOfBirth: p.player.dateOfBirth ? new Date(p.player.dateOfBirth) : null,
4159
+ dateOfBirth: p.player.dateOfBirth ? parseDate(p.player.dateOfBirth) : null,
4165
4160
  heightCm: p.player.heightInCm || null,
4166
4161
  weightKg: p.player.weightInKg || null,
4167
4162
  draftYear: p.player.draftYear ? Number.parseInt(p.player.draftYear, 10) || null : null,
@@ -4241,7 +4236,9 @@ export {
4241
4236
  normaliseVenueName,
4242
4237
  ok,
4243
4238
  parseAflApiDate,
4239
+ parseAflApiMatchTime,
4244
4240
  parseAflTablesDate,
4241
+ parseDate,
4245
4242
  parseFootyWireDate,
4246
4243
  toAestString,
4247
4244
  transformFryziggPlayerStats,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fitzroy",
3
- "version": "1.7.1",
3
+ "version": "1.7.2",
4
4
  "description": "TypeScript port of the fitzRoy R package — programmatic access to AFL data including match results, player stats, fixtures, ladders, and more",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",