This article is part of my #100DaysOfCode and #100DaysOfBlogging challenge. R1D20


Next up in the PHP sidetrack is the Meetup exercise.

A function is provided with the arguments

  • (int) year,
  • (int) month,
  • (string) descriptor and
  • (string) weekday

and expected to process the input and return an instance of DateTime with the corresponding date.

While year, month and weekday are an exact representation of their values, the descriptor is a bit more complicated. It can hold one of the following values:

  • teenth,
  • first,
  • second,
  • third,
  • fourth or
  • last

Thus, making the actual date variable.

Function signature

First, I create the required file meetup.php and declare the function meetup_day() with the known arguments.

function meetup_day(int $year, int $month, string $descriptor, string $weekday) : DateTime
{
    return new DateTime();
}

All tests are failing, but there are no syntax errors - perfect.

Year, month, time

Breaking this exercise up into pieces, I first comment out all but the first test method. That lets me focus on one feature/requirement at a time.

Since there is only a description of a date and the tests assert always against a time of 0, I can start by initializing a DateTime object and setting the time.

$date = new DateTime();
$date->setTime(0, 0);

Next, I add the year and month. Since DateTime::setDate is expecting also the day as third parameter and do not yet know it, I simply declare it as 1.

$date->setDate($year, $month, 1);

Modifier

Now I can use DateTime::modify to set the actual date. I start off by just simply using first as modifier.

$date->modify("first $weekday of this month");

With this implementation, all tests against the first weekday are passing. To handle the other possible values of the descriptor, I first check whether it is equal to 'teenth' since I will need to know that later on again. Then I will use 'first' as modifier for the 'teenth' descriptor. And finally, pass it to the modify() method.

$isTeenth = 'teenth' === $descriptor;
$modifier = $isTeenth ? 'first' : $descriptor;

$date->modify("$modifier $weekday of this month");

Now all tests are passing except for the descriptors with a value of 'teenth'.

Teenth

Note that “monteenth”, “tuesteenth”, etc are all made up words. There was a meetup whose members realized that there are exactly 7 numbered days in a month that end in ‘-teenth’. Therefore, one is guaranteed that each day of the week (Monday, Tuesday, …) will have exactly one date that is named with ‘-teenth’ in every month.

From the exercise description I learn that the day of month for the 'teenth' descriptor has to be between 13 and 19 inclusive. However, it is not possible to know without doing some math, whether it is the second or third weekday in the month.

The simplest solution that comes to my mind, is to add 1 week to the current date, while the day of the month in not in-between 12 and 20.

function modify_teenth_date(DateTime $date) : DateTime
{
    $teenthDays = [
        '13',
        '14',
        '15',
        '16',
        '17',
        '18',
        '19',
    ];
    while (! in_array($date->format('d'), $teenthDays, true)) {
        $date->modify('+1 week');
    }

    return $date;
}

Et voilà, all tests are passing.

My solution

<?php

declare(strict_types = 1);

function meetup_day(int $year, int $month, string $descriptor, string $weekday) : DateTime
{
    $isTeenth = 'teenth' === $descriptor;
    $modifier = $isTeenth ? 'first' : $descriptor;

    $date = new DateTime();
    $date->setDate($year, $month, 1);
    $date->modify("$modifier $weekday of this month");
    $date->setTime(0, 0);

    if ($isTeenth) {
        modify_teenth_date($date);
    }

    return $date;
}

function modify_teenth_date(DateTime $date) : DateTime
{
    $teenthDays = [
        '13',
        '14',
        '15',
        '16',
        '17',
        '18',
        '19',
    ];
    while (! in_array($date->format('d'), $teenthDays, true)) {
        $date->modify('+1 week');
    }

    return $date;
}