Facing the Scary Parts: ‘After Midnight’ Exercise

Callie Buruchara
Launch School
Published in
9 min readOct 20, 2020

In Launch School’s Ruby Track, there’re quite a few courses between RB175 and JS210. That is, there are a lot of courses that aren’t focused on Ruby or JavaScript — the in between land of networks, SQL, and HTML & CSS.

In most of these courses, we take a detour from the comprehensive mastery focus and instead are introduced to immense topics where only parts are curated for mastery. Enough to remind us that not everything programming-related has to be mastered (thank goodness).

Diving back into a mastery as I pick up JavaScript has been both good-familiar and oh-wait-I-forgot-this-is-hard. I’m incredibly thankful to be learning a second language though — because going from “how in the world will I ever use this” to “oh I totally know how to do that” twice is a great way to combat imposter syndrome for me. At least a little bit.

A scary (but necessary) part of mastery is leaning into the situations where you’re super lost instead of avoiding them. Looking closer at code that makes no sense to you instead of being like “Mmm, don’t get it, but whatever, moving on.”

Thus, this blog post is a way of me doing that.

Photo by Tim Gouw on Unsplash

I hated the ‘After Midnight’ exercises when I did them in Ruby. And somehow, I like them even less on the JS time around. (Why can’t I just work with Strings all the time?!) Which is why I’m going to write this blog post explaining the first After Midnight exercise.

It’s kinda long. So if you have to go do something else, I understand. You can still take the earlier lesson and go apply it. Here it is for the rest of you persistent people…

After Midnight Part 1 — Starting Solution Breakdown

For all of the instructions, etc., LS students, you can access the problem explanation here.

  • Task: accept an integer, positive or negative, and turn it into a time that is (a) a String, (b) formatted properly, (c) in military time (d) the time “past” midnight

Before we go about fixing the solution to use the Date object, let’s break down the solution given.

  • On line 1 , we declare a constant global variable MINUTES_PER_HOUR and assign it to the Number with value 60. This makes sense, because we need to know how many minutes are in an hour, and best to not make it a magical number sans label.
  • On line 2, we declare a constant global variable HOURS_PER_DAY and assign it to the Number with value 24. Makes sense for…the same reason as above.
  • On line 3, we declare a constant global variable MINUTES_PER_DAY and assign it to the result of multiplying the values of HOURS_PER_DAY and MINUTES_PER_HOUR. Thus, its value is 1440. Makes sense…
  • On lines 5-15 we use a function declaration to define the timeOfDay function. It accepts one parameter: deltaMinutes . The name of that parameter, if confusing, can be mentally replaced with “elapsedMinutes”. So if the passed in argument is 3, 3 minutes have elapsed. If it’s -3, then negative 3 minutes have elapsed, which means we go back in time from the starting point 3 minutes.

Now let’s follow our function call from line 27 through the functions.

  • On line 6, we reassign deltaMinutes to the result of deltaMinutes % MINUTES_PER_DAY. This operator just returns the remainder of dividing the first operand by the second. Thus, deltaMinutes is now the result of 35 % 1440 which is 35.
  • On lines 7-9, we have an if/else condition. If the new deltaMinutes are negative, we need to reassign it again to the result of some addition. In our case, the block of reassignment isn’t even executed, because 35 is greater than 0.

Let’s pause here a moment. It seems like lines 6–9 are pretty useless, right? After all we just reassigned the value from 35 to… 35. So why the need for this? Two possible scenarios:

  1. If the given argument is greater than 1440 (the amount of minutes in a day), we need to convert it so that we can use it properly. 25 hours (or 1500 minutes) past midnight does not take us to a new dimension of time. It’s just 1AM.
    > And that’s exactly what that operation would show us: 1500 % 1440 === 60, or 60 minutes after midnight, or 1 AM. Thus, this operation keeps the deltaMinutes as is if it works, and corrects it if it doesn’t.
  2. If the given argument is negative (which will still retain its negativity after line 6), then we need to convert it so that we can use it properly. -3 minutes just means 3 minutes before midnight.
    > We could subtract the absolute value of the negative minutes from the number of minutes in a day (1440–3) or add the negative minutes to the number of minutes in a day (1440 + (-3)). It yields the same result.
    > We went with the second option in the code on line 8. Turning the clock back 3 minutes from midnight will yield the same clock-face as adding [24 hours minus three minutes] to midnight.

Thus, after lines 6-9, even if our argument was too big or too small, we have a workable value for deltaMinutes

  • On line 11, we declare a new local variable hours and assign it to the return value of some math configuration.
    > The argument passed to Math.floor is the return value of (in our case) 35 / 60. That gives us a decimal.
    >Math.floor takes that decimal and rounds it down to the nearest integer, which is 0. Thus, the hours is 0.
    > Which makes sense: we’re only going forward 35 minutes which is less than one hour.
  • On line 12, we declare a new local variable minutes and assign it to the return value of deltaMinutes % MINUTES_PER_HOUR.
    > Remember, % in JavaScript gives us the remainder of that division. 35 does not go into 60 cleanly, even once, so the entire thing is the remainder.
    > Which makes sense again, because 35 minutes is less than one hour/60 minutes. Thus, the minutes is 35.

Thus, we have our correct hours and our correct minutes. Now we have to format it properly.

  • On line 14, we return the correct time. We use a separate function to add the zeroes for padding when needed, but then do the : part of it explicitly ourselves.

Let’s look into the padWithZeroes function. We’ll use the (hours, 2) as our two passed in arguments for demonstration purposes.

  • On lines 17-25 we use a function declaration to define the padWithZeroes function, which accepts two parameters: number and length.
    > In our case, inside the function call, number is 0 and length is 2.
    > Fun fact: padWithZeroes is available for our use before it’s declared because of hoisting.
  • On line 18, we declare a new local variable numberString and assign it to the return value of String(number) .
    > This returns the converted-to-a-string-version of number. Thus in our case, numberString is now assigned to '0'.
  • On lines 20-22, we use a while loop to add our necessary 0’s to the numberString. Remember, we currently have '0' but we want to turn it into '00'.
    > While the length of numberString (which is currently 1) is less than the goal amount (which is 2), we add a zero to the front end of the string through reassignment (because you can’t mutate Strings in JS).
    > This only happens once, and we have what we need: '00'.
    > We could have hardwired it into the padWithZeroes function to have a goal length of 2 instead of making it a separate parameter; however, it’s a good idea to do it this way in case we want to reuse the padWithZeroes function in another way where we have a different length in mind. It also makes the function’s goal more clear.
  • Going back to line 14, we see that we would then be returning: '00:35' which is…exactly what we want.

We’re done! 🙌

Intermission

Well, I don’t know about you, but I sure understand that problem a whole lot more. It’s actually pretty simple and straightforward once you take it piece by piece.

This is a reminder to myself that if I just slow down and take it piece by piece, chances are that it’ll be a lot easier to understand. Onward!

After Midnight Part 1 — Given Solution Breakdown

Instead of walking through the process of creating the solution, I’m going to break down the given solution. This solution takes advantage of the Date object.

First note: the padWithZeroes function is the exact same, so we’ll skip over explaining that part. We’ll focus on the differences. Also, in our example, we’ll still pass in 35 as an argument to the function call.

  • We no longer have the three constants at the top for minutes per hour, hours per day, and minutes per day.
  • Instead, we have one constant declared on line 1 which is self-explanatory. MILLISECONDS_PER_MINUTE represents just that (1 second is 1000 milliseconds)

Notice that the timeOfDay function is a lot shorter, and we basically just declare a lot of new constants. Let’s look closer.

  • In the timeOfDay function definition, on line 4 we establish our “starting position”: midnight. The January 1st 2000 part is kinda irrelevant here, since we’re just looking at the time. But in order to create a new Date object, we need to supply those details.
  • On line 5 we declare a new local variable afterMidnight and set it to the result of some configuration with the Date object. Let’s break that down.
    > new Date : we’re creating a new Date object
    > midnight.getTime() : our starting point is the time from the Date object created one line above
    > + deltaMinutes * MILLISECONDS_PER_MINUTE: we add to that midnight starting point the deltaMinutes (35 in our case) multiplied by 60000.
    > Thus, our afterMidnight is assigned to 2000–01–01T00:35:00.000Z.
  • On lines 6 & 7 we use the Date object methods getHours() and getMinutes() which do just that, pass them into padWithZeroes, and voila. They’re formatted.
  • On line 9 we return the hours and minutes formatted properly with the : in between them.

Why Milliseconds?

We can see how using the Date object greatly simplified this solution. But why did we need to use milliseconds? Isn’t that kind of random?

Well, it’s just the way it is. According to MDN:

JavaScript Date objects represent a single moment in time in a platform-independent format. Date objects contain a Number that represents milliseconds since 1 January 1970 UTC.

A pretty irrelevant to our current use case, but fun fact about the Date object:

It should be noted that the maximum Date is not of the same value as the maximum safe integer (Number.MAX_SAFE_INTEGER is 9,007,199,254,740,991). Instead, it is defined in ECMA-262 that a maximum of ±100,000,000 (one hundred million) days relative to January 1, 1970 UTC (that is, April 20, 271821 BCE ~ September 13, 275760 CE) can be represented by the standard Date object (equivalent to ±8,640,000,000,000,000 milliseconds).

Because of the use of milliseconds, the formatting of the Date object (into a useful format) takes some intentionality. For example, if I want to know the current date, I might think to use Date.now(). All that gives me, though, is the number of milliseconds elapsed since January 1, 1970 (with leap seconds ignored).

Running Date.now() gives me this: 1603044899619. Not very readable. This is why Date methods like getHours(), getFullYear(), getTime(), and getMonth(), etc. are so handy. They do all of the parsing and conversion for you. But in order to add or subtract time properly, we need to do it with milliseconds.

This is why we had to convert the minutes past (or before) midnight into milliseconds by multiplying the number of minutes by the number of milliseconds in a minute (remember the 35 * 60000?).

I hope you found this helpful; I sure did. And even if you didn’t need help on this exercise, I hope you take the idea with you to lean into the difficult parts. Does that mean you need to write a too long blog post about every exercise that gives you issues? Nah, not really. It just means you should take the time to really understand.

Even if that one programming detail never comes up in the future, the necessity of perseverance and mastery always will.

--

--