From 7625ff01a0bdb80dd68de096899da15b6aa8d30f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 17:53:36 +0000 Subject: [PATCH 1/8] Initial plan From 1408e73c471d26875cf9600b1b3e0a6d5343c344 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 18:04:30 +0000 Subject: [PATCH 2/8] Add calendar.ics and date parser plugin for downloadable calendar Co-authored-by: ckenst <6896787+ckenst@users.noreply.github.com> --- _plugins/date_parser.rb | 91 +++++++++++++++++++++++++++++++++++++++++ calendar.ics | 27 ++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 _plugins/date_parser.rb create mode 100644 calendar.ics diff --git a/_plugins/date_parser.rb b/_plugins/date_parser.rb new file mode 100644 index 00000000..11e7be51 --- /dev/null +++ b/_plugins/date_parser.rb @@ -0,0 +1,91 @@ +require 'date' + +module Jekyll + module DateParser + # Parse human-readable date strings into structured dates + # Examples: + # "January 22, 2026" -> { start_date: "20260122", end_date: "20260122" } + # "January 29 - February 1, 2026" -> { start_date: "20260129", end_date: "20260201" } + # "February 9-13, 2026" -> { start_date: "20260209", end_date: "20260213" } + + def parse_conference_dates(date_string) + return nil if date_string.nil? || date_string.empty? + + # Remove extra spaces and quotes + date_string = date_string.to_s.strip.gsub(/^"|"$/, '') + + begin + # Pattern: "Month Day, Year" (single day) + if date_string =~ /^([A-Za-z]+)\s+(\d+),\s+(\d{4})$/ + month = $1 + day = $2.to_i + year = $3.to_i + start_date = Date.parse("#{year}-#{month}-#{day}") + # For ICS format, DTEND should be the day after the event ends + end_date = start_date + 1 + return { + 'start_date' => start_date.strftime('%Y%m%d'), + 'end_date' => end_date.strftime('%Y%m%d') + } + end + + # Pattern: "Month Day-Day, Year" (e.g., "February 9-13, 2026") + if date_string =~ /^([A-Za-z]+)\s+(\d+)-(\d+),\s+(\d{4})$/ + month = $1 + start_day = $2.to_i + end_day = $3.to_i + year = $4.to_i + start_date = Date.parse("#{year}-#{month}-#{start_day}") + end_date = Date.parse("#{year}-#{month}-#{end_day}") + # For ICS format, DTEND should be the day after the event ends + end_date = end_date + 1 + return { + 'start_date' => start_date.strftime('%Y%m%d'), + 'end_date' => end_date.strftime('%Y%m%d') + } + end + + # Pattern: "Month Day - Month Day, Year" (e.g., "January 29 - February 1, 2026") + if date_string =~ /^([A-Za-z]+)\s+(\d+)\s+-\s+([A-Za-z]+)\s+(\d+),\s+(\d{4})$/ + start_month = $1 + start_day = $2.to_i + end_month = $3 + end_day = $4.to_i + year = $5.to_i + start_date = Date.parse("#{year}-#{start_month}-#{start_day}") + end_date = Date.parse("#{year}-#{end_month}-#{end_day}") + # For ICS format, DTEND should be the day after the event ends + end_date = end_date + 1 + return { + 'start_date' => start_date.strftime('%Y%m%d'), + 'end_date' => end_date.strftime('%Y%m%d') + } + end + + # Pattern: "Month Day-Day - Month Day, Year" (e.g., "April 26 - May 1, 2026") + if date_string =~ /^([A-Za-z]+)\s+(\d+)\s+-\s+([A-Za-z]+)\s+(\d+),\s+(\d{4})$/ + start_month = $1 + start_day = $2.to_i + end_month = $3 + end_day = $4.to_i + year = $5.to_i + start_date = Date.parse("#{year}-#{start_month}-#{start_day}") + end_date = Date.parse("#{year}-#{end_month}-#{end_day}") + return { + 'start_date' => start_date.strftime('%Y%m%d'), + 'end_date' => end_date.strftime('%Y%m%d') + } + end + + # If we can't parse the date, return nil + return nil + rescue => e + # If date parsing fails, return nil + Jekyll.logger.warn "Date parsing failed for: #{date_string} - #{e.message}" + return nil + end + end + end +end + +Liquid::Template.register_filter(Jekyll::DateParser) diff --git a/calendar.ics b/calendar.ics new file mode 100644 index 00000000..88cdadc1 --- /dev/null +++ b/calendar.ics @@ -0,0 +1,27 @@ +--- +layout: null +permalink: /calendar.ics +--- +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:https://testingconferences.org/ +METHOD:PUBLISH +CALSCALE:GREGORIAN +X-WR-CALNAME:Testing Conferences +X-WR-TIMEZONE:UTC +X-WR-CALDESC:Software Testing Conferences and Workshops +{% for event in site.data.current %}{% assign parsed_dates = event.dates | parse_conference_dates %}{% if parsed_dates %} +BEGIN:VEVENT +UID:{{ parsed_dates.start_date }}{{ event.name | remove: ' ' | remove: ',' | remove: ':' | remove: '(' | remove: ')' }}@testingconferences.org +SUMMARY:{{ event.name }} +LOCATION:{{ event.location | replace: ",", "\," }} +DESCRIPTION:{{ event.url }}{% if event.twitter %}\nTwitter: https://twitter.com/{{ event.twitter }}{% endif %} +CLASS:PUBLIC +URL:{{ event.url }} +DTSTART;VALUE=DATE:{{ parsed_dates.start_date }} +DTEND;VALUE=DATE:{{ parsed_dates.end_date }} +STATUS:CONFIRMED +TRANSP:TRANSPARENT +END:VEVENT +{% endif %}{% endfor %} +END:VCALENDAR From 29abe33d8fe2e074e285f8c221d514db66ed45a6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 18:05:36 +0000 Subject: [PATCH 3/8] Add calendar download link to footer Co-authored-by: ckenst <6896787+ckenst@users.noreply.github.com> --- _includes/footer.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/_includes/footer.html b/_includes/footer.html index 66e0e51e..34f9e58e 100644 --- a/_includes/footer.html +++ b/_includes/footer.html @@ -6,6 +6,9 @@ + From 94223887eaf761a67697598991e2c7f961cdba6c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 18:25:07 +0000 Subject: [PATCH 4/8] Add GitHub Actions deployment workflow and update documentation Co-authored-by: ckenst <6896787+ckenst@users.noreply.github.com> --- .github/workflows/deploy.yml | 60 ++++++++++++++++++++++++++++++++++++ README.md | 19 ++++++++++++ _config.yml | 1 + calendar.ics | 6 ++-- 4 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..c5ba31ec --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,60 @@ +name: Deploy Jekyll site to Pages + +on: + push: + branches: ["master"] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.1' + + - name: Install Jekyll and dependencies (without github-pages constraints) + run: | + gem install bundler -v 2.4.17 + gem install jekyll -v '~> 4.3' + gem install jekyll-sitemap + gem install jekyll-seo-tag + gem install jekyll-feed + gem install kramdown + gem install webrick + + - name: Setup Pages + id: pages + uses: actions/configure-pages@v5 + + - name: Build with Jekyll (custom plugins enabled) + run: jekyll build --baseurl "${{ steps.pages.outputs.base_path }}" + env: + JEKYLL_ENV: production + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/README.md b/README.md index 9ee43854..1de7188b 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,25 @@ A good _heuristic_ for whether a conference should be included is if its name in Don't forget to **[sign up](http://eepurl.com/c4paYT)** for our once **monthly newsletter.** +## Calendar Download + +The site provides a downloadable calendar file (iCal/ICS format) that can be imported into calendar applications like Google Calendar, Apple Calendar, or Outlook. The calendar is automatically generated from the conference data and is available at: + +``` +https://testingconferences.org/calendar.ics +``` + +This feature allows users to easily import all upcoming testing conferences into their calendar application. + +## Deployment + +The site is built and deployed using GitHub Actions, which allows the use of custom Jekyll plugins. The workflow: +1. Runs on every push to the `master` branch +2. Builds the Jekyll site with all custom plugins +3. Deploys the static site to GitHub Pages + +Custom plugins (like the date parser for the calendar feature) are stored in the `_plugins` directory and are fully supported through this deployment method. + ## License TC.org is released under the [MIT License](MIT-LICENSE). diff --git a/_config.yml b/_config.yml index e1df2ea8..f32a87cb 100644 --- a/_config.yml +++ b/_config.yml @@ -13,6 +13,7 @@ plugins: # Build settings markdown: kramdown +safe: false defaults: - values: author: "Chris Kenst" diff --git a/calendar.ics b/calendar.ics index 88cdadc1..734a535f 100644 --- a/calendar.ics +++ b/calendar.ics @@ -12,14 +12,14 @@ X-WR-TIMEZONE:UTC X-WR-CALDESC:Software Testing Conferences and Workshops {% for event in site.data.current %}{% assign parsed_dates = event.dates | parse_conference_dates %}{% if parsed_dates %} BEGIN:VEVENT -UID:{{ parsed_dates.start_date }}{{ event.name | remove: ' ' | remove: ',' | remove: ':' | remove: '(' | remove: ')' }}@testingconferences.org +UID:{{ parsed_dates["start_date"] }}{{ event.name | remove: ' ' | remove: ',' | remove: ':' | remove: '(' | remove: ')' }}@testingconferences.org SUMMARY:{{ event.name }} LOCATION:{{ event.location | replace: ",", "\," }} DESCRIPTION:{{ event.url }}{% if event.twitter %}\nTwitter: https://twitter.com/{{ event.twitter }}{% endif %} CLASS:PUBLIC URL:{{ event.url }} -DTSTART;VALUE=DATE:{{ parsed_dates.start_date }} -DTEND;VALUE=DATE:{{ parsed_dates.end_date }} +DTSTART;VALUE=DATE:{{ parsed_dates["start_date"] }} +DTEND;VALUE=DATE:{{ parsed_dates["end_date"] }} STATUS:CONFIRMED TRANSP:TRANSPARENT END:VEVENT From e54eb2b29bfa16c816b9ec20a14d14fe3530054c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 18:26:22 +0000 Subject: [PATCH 5/8] Fix duplicate pattern and ICS compliance in date parser Co-authored-by: ckenst <6896787+ckenst@users.noreply.github.com> --- _plugins/date_parser.rb | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/_plugins/date_parser.rb b/_plugins/date_parser.rb index 11e7be51..0523bc3a 100644 --- a/_plugins/date_parser.rb +++ b/_plugins/date_parser.rb @@ -62,21 +62,6 @@ def parse_conference_dates(date_string) } end - # Pattern: "Month Day-Day - Month Day, Year" (e.g., "April 26 - May 1, 2026") - if date_string =~ /^([A-Za-z]+)\s+(\d+)\s+-\s+([A-Za-z]+)\s+(\d+),\s+(\d{4})$/ - start_month = $1 - start_day = $2.to_i - end_month = $3 - end_day = $4.to_i - year = $5.to_i - start_date = Date.parse("#{year}-#{start_month}-#{start_day}") - end_date = Date.parse("#{year}-#{end_month}-#{end_day}") - return { - 'start_date' => start_date.strftime('%Y%m%d'), - 'end_date' => end_date.strftime('%Y%m%d') - } - end - # If we can't parse the date, return nil return nil rescue => e From e9fd405f9d00e1862ccdc22cd6cddecf47e26b3d Mon Sep 17 00:00:00 2001 From: Chris Kenst Date: Tue, 6 Jan 2026 10:35:31 -0800 Subject: [PATCH 6/8] Rearranging footer list --- _includes/footer.html | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/_includes/footer.html b/_includes/footer.html index 34f9e58e..f8c84532 100644 --- a/_includes/footer.html +++ b/_includes/footer.html @@ -6,9 +6,6 @@ - @@ -25,7 +22,7 @@