Skip to content

othyn/go-calendar

Repository files navigation

GO Calendar

Lint Generate Calendars Deploy Site Automated Release GitHub all releases

An automated unofficial iCal calendar generator for Pokémon GO events powered by Leek Duck, so you can easily have a Pokémon GO calendar on your phone (iOS & Android) or computer (Linux, macOS & Windows) that is always up-to-date.

Take a look at the website to get setup with the calendar.

Sources & Credit

All events are sourced from Leek Duck via bigfoott/ScrapedDuck, using the events JSON resource.

This project just aims to take all that brilliant hard work and dedication from the Leek Duck team, that is presented oh-so nicely on the Leek Duck website, and create a highly convenient and easily consumable auto-updating iCal calendar for use in any modern calendar client.

Development

Project is just a simple PHP CLI script using a few Symfony libraries for the sake of convenience, not the best and far from optimal, but fast and easy to maintain. The idea is to just have it get executed every 24 hours by a GitHub Action workflow, in which it updates the output fragment of ./dist/gocal.ics and commits it back to the main repo branch. This keeps the URL consistent so in theory calendar clients should be able to subscribe to it.

There is a makefile in the root of the project that does most of the heavy lifting. A brief overview of the current makefile:

# Build the relevant Docker containers for the project
make build

# Bring up/define in waiting the Docker containers for the project in detached mode
make up

# Tear down the running containers
make down

# Tear down and bring back up the containers for the project, shortcut for:
# $ make down && make up
make restart

# Install the project dependencies via the project container
make install

# Lint the projects code via the project container
make lint

# Generate the calendar file into dist/gocal.ics via the project container
make gen

# Build the pages site into a static HTML file in pages/dist
make site

# Generate the calendar then build the pages site
make all

As for the code itself, bin/gocal is the entrypoint of this CLI tool. It will bootstrap the Symfony console library and pull in the instructed commands from src/Commands.

Hopefully the code should be clear enough to be self documenting, although I'm not usually one to rely on such things alone, given the relative size of the project and associated debug output lines enclosing code blocks, it should be fine in this circumstance. If the project grows any larger, then I'll produce the appropriate documentation to support it.

A note on time zones and local time

This was a pain in the arse to solve, and I went through a lot of iterations coming up with a working solution. I had to get my hands dirty and go digging, looking at the generated date output formats in the iCal file and delve into the Spatie iCal library source to figure out what it was doing that it shouldn't be.

The first clue was in the generated dates in the output iCal file, any start and end dates that also had times were suffixed with a Z. According to this SO post, any start and end dates with times that end with Z will be interpreted in a UTC timezone, regardless of the timezone - or lack thereof - in the calendar or event.

Next step, we need to remove the Z by finding out where and how its being added.

Turns out in \Spatie\IcalendarGenerator\Properties\DateTimeProperty::getValue if the DateTime entity passed is in UTC (which is the default in PHP if no time zone is specified when creating a DateTime entity), regardless of the state of withoutTimezone(), getValue will append Z to the generated DateTimeValue as its only checks are if the DateTime is in UTC and has a time associated with it. The calendar clients will then interpret this as a set UTC time zone when in fact we didn't want that as we provided no initial time zone and removed time zones with withoutTimezone(). The Spatie documentation for spatie/icalendar-generator doesn't specify this behaviour, and I believe this to be a bug in the library... kinda. More on that below as I worked on a potential PR only to hit a gotcha scenario.

To illustrate the issue, this is the current code:

public function getValue(): string
{
    return $this->isUTC() && $this->dateTimeValue->hasTime()
        ? "{$this->dateTimeValue->format()}Z"
        : $this->dateTimeValue->format();
}

... and this is what I think it should be:

// Expose the existing $withoutTimeZone parameter captured in the __construct as a class property
private bool $withoutTimeZone;

public function getValue(): string
{
    return $this->isUTC() && $this->dateTimeValue->hasTime() && ! $this->withoutTimeZone
        ? "{$this->dateTimeValue->format()}Z"
        : $this->dateTimeValue->format();
}

This as we should only be adding the Z suffix if the time zone has been explicitly set, otherwise we are getting the UTC time zone set and thus interpreted by default, which is not intended if you've specified withoutTimeZone() on DateTime's initialised with no time zone, thus defaulting to UTC.

The workaround being to simply set a random time zone on the DateTime being created and then use withoutTimeZone(), as it will correctly strip out/not generate any time zone related properties making all dates interpret as intended - local time.

Now, back to that kinda, that lovely gotcha. I've had a go at patching this bug and submitting a PR for it, and now I see their problem, and it doesn't really have a clean solution. UTC DateTime's are used for internal time zone calculations, mainly for setting boundaries to expected time zone shifts, such as during summer. Patching it with the above fix solves the issues with implicit/default UTC usage and withoutTimeZone() used in combination, but breaks all DTSTAMP generation which needs to be UTC based. This is as internally they are using withoutTimeZone() to ensure that all user defined time zones are stripped from these internal DateTime's, to normalise them onto a UTC base, as is required for fixed points in time such as time zone boundaries. Its not an easy solve without some major reworking of how dates are safely handled for time zone boundaries in the library.

For now, I'm going to leave my hack in this repo. Setting the time zone to GMT on the event dates in combination with using withoutTimeZone() allows the library to skip the UTC logic, giving the desired effect of local times on the events due to the UTC Z suffix and all time zone stuff being dropped from the generated iCal file. I'll maybe submit a PR just with a documentation change to alert other future users of the library to this gotcha scenario, although I need to find a good way of phrasing it before I do so, as the above is a bit of a brain-teaser.

Legal

All rights reserved by their respective owners.

This project is not officially affiliated with Pokémon GO and is intended to fall under Fair Use doctrine, similar to any other informational site such as a wiki.

Pokémon and its trademarks are ©1995-2022 Nintendo, Creatures, and GAMEFREAK.

All images and names owned and trademarked by Nintendo, Niantic, The Pokémon Company, and GAMEFREAK are property of their respective owners.

About

A community driven auto-updating Pokémon GO events calendar that you can subscribe to in any calendar app on your phone/PC. Powered by Leek Duck.

Topics

Resources

License

Code of conduct

Security policy

Stars

Watchers

Forks

Sponsor this project