Knowing When to Renew your Vehicle's Registration in the Philippines🇵🇭 using Python

5 min read
Knowing When to Renew your Vehicle's Registration in the Philippines🇵🇭 using Python
Update: Feb 13, 2022:
- New demo deployed at https://mvrs.ron.sh/
- Updated example code (with refactors): https://github.com/roniemartinez/motor-vehicle-registration-schedule-ph

As a car owner in the Philippines, I need to renew my car's registration every year. All car owners are required by the LTO (Land Transportation Office) to follow a specific schedule for the renewal of their car's registration. Where exactly they can find it? It's all on the license plate!


Is it written on the license plate?

Your vehicle registration schedule relies on the last 2 digits of you car's license plate. For example, if your license plate is ABC 123, then you need to take note of the digits 2 and 3.

The last digit indicates which month you are scheduled for renewal.

DigitMonth
1January
2February
3March
4April
5May
6June
7July
8August
9September
0October

The second digit to the last indicates the days of the week.

DigitsDays
1 2 31st to 7th
4 5 68th to 14th
7 815th to 21st
9 022nd onwards

So for the license plate ABC 123, the schedule for renewal of registration should be any of the business days from the 1st to 7th of March.


Not everyone knows about it!

Okay, I admit it! I was not aware of it until a friend asked me when is the renewal of my vehicle's registration. My car is only 2 years old though as of this writing. Vehicle registrations are valid for 3 years on the first registration. Afterwards, renewal will be yearly.


Is there an app for that?

Maybe.


Yeah! Let's make an app!

I am going to make a Python application that is accessible for everyone.

First we will need to get the days, the month and the year. In case the schedule for the current year have passed, the schedule for next year will be presented to the user.

Let's group the days first into 4 lists. We will require year and month as parameters since not all months have the same number of days. We will acquire the number of days from the method calendar.monthrange().

from calendar import monthrange


def get_day_groups(year, month):
    day_groups = [[], [], [], []]
    for i in range(1, monthrange(year, month)[1] + 1):
        try:
            day_groups[(i - 1) // 7].append(i)
        except IndexError:
            day_groups[3].append(i)
    return day_groups

License plates in the Philippines have different formats! There is ABC 123, AB 1234, 1234 AB, etc. and we need to match everything. For this, we will be using regular expression. The pattern below should match all the license plate format.

For the list of formats, click here.

import re

plate_number_pattern = re.compile(r'([a-zA-Z]{2,3}[\s-]?(\d{2,5}))|((\d{4})[\s-]?[a-zA-Z]{2})')

Give the license plate, the function get_schedule() below, should return the days, month, and year. I will discuss each important part one by one.

from datetime import datetime
from pytz import timezone


def get_schedule(license_plate):
    a, b = plate_number_pattern.match(license_plate).groups()[1::2]
    week_digit, month_digit = map(int, (a or b)[-2:])
    month = month_digit or 10
    week_index = dict(zip(range(10), [3, 0, 0, 0, 1, 1, 1, 2, 2, 3]))[week_digit]
    now = datetime.now(tz=timezone('Asia/Manila'))
    year = now.year if now.month <= month else now.year + 1
    day_groups = get_day_groups(year, month)
    days = [d for d in day_groups[week_index] if datetime(year, month, d).isoweekday() < 6]
    if now.month == month and all([now.day > day for day in days]):
        year += 1
        days = [d for d in day_groups[week_index] if datetime(year, month, d).isoweekday() < 6]
    return days, month, year

Calling groups() method from plate_number_pattern.match() should return a tuple of 4 objects (could be strings or None). We are only interested on the values of the 2nd and the 4th objects. We sliced it using [1::2] and saved the values to variables a and b.

a, b = plate_number_pattern.match(license_plate).groups()[1::2]

Our digits could be contained on either a or b. I used the simple logic a or b since only one of these contain our data. I converted the digit characters (string) to integers using map(). The second line simply changes 0 to 10.

week_digit, month_digit = map(int, (a or b)[-2:])
month = month_digit or 10

To get the days, we will use be using a dictionary. The code below creates a dictionary based on our second table above and acquires the row index. 1st row is 0, 2nd row is 1, and so on.

week_index = dict(zip(range(10), [3, 0, 0, 0, 1, 1, 1, 2, 2, 3]))[week_digit]

Since our server is hosted somewhere in the US (coz AWS), we will need to acquire the current date in the Philippines. This can be done with the help of the library pytz.

now = datetime.now(tz=timezone('Asia/Manila'))

Based on the current Philippine time, we will calculate the next schedule. I the days or month have passed, we will calculate the schedule for next year.

year = now.year if now.month <= month else now.year + 1
day_groups = get_day_groups(year, month)
days = [d for d in day_groups[week_index] if datetime(year, month, d).isoweekday() < 6]
if now.month == month and all([now.day > day for day in days]):
    year += 1
    days = [d for d in day_groups[week_index] if datetime(year, month, d).isoweekday() < 6]
return days, month, year

Presenting it to the end-users

Remember my previous blog about how I built Atom? I will be using the same concept - deploy in Flask using Flask-WTForms.

First, I created a form with validators and a reCAPTCHA.

from flask_wtf import FlaskForm, RecaptchaField
from wtforms import SubmitField, StringField
from wtforms.validators import InputRequired, Regexp


class VehicleRegistrationScheduleForm(FlaskForm):
    license_plate = StringField(
        'License Plate',
        validators=[
            InputRequired(),
            Regexp(plate_number_pattern, message='Does not match any accepted license plate format')
        ]
    )
    recaptcha = RecaptchaField()
    get_schedule = SubmitField('Get Schedule')

Second, I made a Flask application, created a route /motor-vehicle-registration-schedule, and put them all together, I wanted to present a calendar to the user so I made use of calendar.HTMLCalendar() and BeautifulSoup to customize my calendar to work with Bootstrap.

from flask import Blueprint, request, render_template
from bs4 import BeautifulSoup

application = Flask(__name__)


@application.route('/motor-vehicle-registration-schedule', methods=['GET', 'POST'])
def motor_vehicle_registration_schedule():
    form = VehicleRegistrationScheduleForm()
    has_schedule = False
    days = []
    if request.method == 'POST' and form.validate_on_submit():
        try:
            days, month, year = get_schedule(form.license_plate.data)
            calendar = HTMLCalendar(firstweekday=6)
            has_schedule = calendar.formatmonth(year, month)
            soup = BeautifulSoup(has_schedule, 'html.parser')
            for table in soup.find_all('table'):
                table['class'] = table.get('class', []) + ['table', 'table-responsive', 'table-sm', 'd-flex', 'justify-content-center']
            for day in soup.find_all('td'):
                text = day.getText().strip()
                if text.isdigit() and int(text) in days:
                    day['class'] = day.get('class', []) + ['table-danger']
            has_schedule = soup
        except AttributeError:
            has_schedule = None
    return render_template('motor-vehicle-registration-schedule.html', form=form, has_schedule=has_schedule, days=days)

Third, I wrote a HTML template. I removed the Bootstrap-related parts to show the most basic parts that we need.

{% if has_schedule %}
    <h5>You are scheduled for renewal on these dates:</h5>
    {{ has_schedule | safe }}
    <h5>Check Another License Plate</h5>
{% endif %}
<form method="POST" action="{{ url_for('motor_vehicle_registration_schedule') }}"
      autocomplete="off">
    {{ form.csrf_token }}
    {{ form.license_plate(placeholder='Enter License Plate') }}
    {{ form.recaptcha() }}
    {{ form.get_schedule() }}
</form>

The final product

Upon clicking GET SCHEDULE button, a calendar for the renewal of vehicle's registration based on the user input will be shown.


What's next?

What about holidays??? Yes of course but not today! ? Maybe in the future. Holidays in the Philippines are, for me, somewhat unpredictable. There are already declared holidays, but they kept on changing and adding new special holidays. If there is a good API for that somewhere.

As an engineer, I am very curious and starting to question this scheduling format:

  1. Why are there no schedules for November and December?
  2. The last weeks of each month (except February) have the longest days. Why only 2 digits are catered on these days?

Comment below if you know the answer!

Read more articles like this in the future by buying me a coffee!

Buy me a coffeeBuy me a coffee