2015-10-14

Dates and Division

Most modern operating systems have a basic scheduler built in. In UNIX, this is the cron command, and it can run a program based on the minute, hour, day of week, day of month, and/or month. So it lets you set a program to run every hour at 15 minutes past the hour, or every Monday, or every month on the first of the month.

But what if you want to run something every five days? For example, we may want a reminder that we need to make a phone call every so often, no matter the day of the week, or set our TARDIS to make a stop every other day. The cron scheduler doesn't have a way to specify this, and neither do many other scheduling systems. The reason is clear - our week has a prime number of days (seven) so isn't easily divisible into smaller units. And our months have inconsistent lengths, so we can't easily divide them up either.

Coding is about problem solving, and we can write some code to handle this. We'll take advantage of the built-in scheduler and then two things you can find in most programming environments:

  1. A way to determine the number of seconds since a known point in time.
  2. Integer mathematics, which allow us to do calculations against the number of seconds.
Let's look at each and how they help us solve our problem.

Epoch Time

Times, dates, and calendars are a tricky problem for computers because the method we have evolved, as humans, to deal with them are somewhat inconsistent. The full reasons why would be good for a totally separate blog, but we'll learn about some of them as we explore how they impact us as coders.

For now, we need a way to measure how many consistent time units there have been since a fixed point in time. Months aren't consistent since they have a variable number of days. Weeks aren't bad, but they don't divide very well. Days would be good, but even better would be the number of Seconds since it is of small granularity and can be used consistently. (Well, mostly consistently. Leap Seconds are a bit of a problem... but one that most people ignore. We'll talk about them another time.)

Most systems can provide the number of seconds since some specific point in time. This point is known as the "epoch time" and varies based on the programming environment and operating system. In UNIX, the epoch moment is 1 Jan 1970 at midnight UTC while Windows uses 1 Jan 1601 and other systems provide other reference point dates. I'm going to use UNIX epoch time in my examples, but the principles hold true for all of them.

Given this, 4:00 pm UTC on October 14th was 1444838400 seconds since the start of the UNIX epoch. If we want to verify this, we can run this command at the UNIX command line:
date -j -u 201510141600.00 "+%+ %s"

We can omit the -j and -u parameters and the time specification and change the format a bit to get the current number of seconds since the epoch. This can run in a cron job and gives us some representation of "now". That's a good start, but how can we turn this into something that happens every three days? For that, we'll need a touch of very simple math.

Divide and Conquer

Since we're going to be dealing with days, we'll need to convert seconds into days. Not a problem - we just need to know how many seconds are in a day (60 seconds * 60 minutes * 24 hours = 86400 seconds). Take the total number of seconds (1444838400 in our example above) and divide it by 86400 and we get... 16722.6666667.

That fractional part is irritating, however. It would be more useful if we knew the number of whole days. Most languages have a few ways to deal with this, and we just need to pick the best way to do so:
  • We can round the number to the nearest whole number of days (16723 in our example).
  • We can truncate the number by removing the fractional part (16722 in our example).
  • Some languages have "integer division", which won't return any fractional part at all, which will work much like dividing and truncating.

We're going to assume integer division in this case since it is consistent with some other math we'll be doing in a minute.

We now have the number of days in the epoch. So what? This isn't really what we want - we want to know which day of our five-day cycle we're in, given our example. Well, we divided by the number of seconds in a day to get days... so we should just divide it by 5 (using integer division) to get what we need, right? Doing so, we'll get 3344, which tells us that there have been 3344 five-day-cycles since the start of the epoch.

Which doesn't really help us. What we need to know is which day in the cycle we're in. For this, we're going to need to use a different bit of integer math called the modulo (which we sometimes shorten as mod). This is a fancy way of saying "the remainder when we divide one number by another". So 16722 mod 5 is 2.

Not all environments have a modulo operator (tho I'm baffled why not), however, but reproducing this is fairly easy given other integer operators. Remember that 3344 result we got earlier that didn't seem useful? Turns out, we can use it to get the remainder if we really needed to. Integer division means that if we take a number, divide it by a value, and then multiply it by that same value again, we may get a different result than the original number. What is the difference between the two? The remainder. We can represent this as something like:
remainder = value - ( value / divisor )
(Assuming, of course, you're doing integer division. Normal division isn't very useful here.)

Either way, we now have a remainder of 2 given our example. What does this mean? That it is the third day of the five-day-cycle we're interested in.

Wait... third day? Not the second? Nope. The first day would have a remainder of 0.

Putting it together

Now that we know which day in the cycle we're in, what do we do? Well, that depends on what we want to do. If we needed to remind us to make a phone call every five days at noon, we might do the following:
  • Write a program that checked which day in the cycle it was (as above) and, if it was the day in the cycle we needed, send us an email. If it isn't - it should do nothing.
  • Setup a cron job to call that program every day at noon. The cron job should run every day and leave the cycle-checking to the program.
That's it!

This concept of breaking a problem down into smaller parts is a common one in coding - we'll see it many more times as we explore topics. And the trick of using modulo to break down a big counter into a smaller cycle is also a good one to keep in mind - you'll see this trick used, for example, where you want to group things into clusters of a known size and need to know when you're at the beginning of the cluster.

Most important, however, is developing a solid approach to problem solving and breaking down a task to figure out the best way to approach it. That is the core of good coding.


No comments:

Post a Comment