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).
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.
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.
line 1, we declare a constant global variable
MINUTES_PER_HOURand 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.
line 2, we declare a constant global variable
HOURS_PER_DAYand assign it to the Number with value
24. Makes sense for…the same reason as above.
line 3, we declare a constant global variable
MINUTES_PER_DAYand assign it to the result of multiplying the values of
MINUTES_PER_HOUR. Thus, its value is
1440. Makes sense…
lines 5-15we use a function declaration to define the
timeOfDayfunction. 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.
line 6, we reassign
deltaMinutesto the result of
deltaMinutes % MINUTES_PER_DAY. This operator just returns the remainder of dividing the first operand by the second. Thus,
deltaMinutesis now the result of
35 % 1440which is 35.
lines 7-9, we have an if/else condition. If the new
deltaMinutesare 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
35is 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:
- 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
deltaMinutesas is if it works, and corrects it if it doesn’t.
- 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.
lines 6-9, even if our argument was too big or too small, we have a workable value for
line 11, we declare a new local variable
hoursand assign it to the return value of some math configuration.
> The argument passed to
Math.flooris the return value of (in our case)
35 / 60. That gives us a decimal.
Math.floortakes that decimal and rounds it down to the nearest integer, which is
0. Thus, the hours is
> Which makes sense: we’re only going forward
35minutes which is less than one hour.
line 12, we declare a new local variable
minutesand assign it to the return value of
deltaMinutes % MINUTES_PER_HOUR.
35does not go into
60cleanly, 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
Thus, we have our correct hours and our correct minutes. Now we have to format it properly.
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.
lines 17-25we use a function declaration to define the
padWithZeroesfunction, which accepts two parameters:
> In our case, inside the function call,
> Fun fact:
padWithZeroesis available for our use before it’s declared because of hoisting.
line 18, we declare a new local variable
numberStringand assign it to the return value of
> This returns the converted-to-a-string-version of
number. Thus in our case,
numberStringis now assigned to
lines 20-22, we use a
whileloop to add our necessary 0’s to the
numberString. Remember, we currently have
'0'but we want to turn it into
> 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:
> We could have hardwired it into the
padWithZeroesfunction 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
padWithZeroesfunction 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! 🙌
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
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 1which is self-explanatory.
MILLISECONDS_PER_MINUTErepresents 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
timeOfDayfunction definition, on
line 4we 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
Dateobject, we need to supply those details.
line 5we declare a new local variable
afterMidnightand set it to the result of some configuration with the
Dateobject. Let’s break that down.
new Date: we’re creating a new
midnight.getTime(): our starting point is the time from the
Dateobject created one line above
+ deltaMinutes * MILLISECONDS_PER_MINUTE: we add to that midnight starting point the
deltaMinutes(35 in our case) multiplied by
> Thus, our
afterMidnightis assigned to
lines 6 & 7we use the
getMinutes()which do just that, pass them into
padWithZeroes, and voila. They’re formatted.
line 9we return the hours and minutes formatted properly with the
:in between them.
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:
Dateobjects represent a single moment in time in a platform-independent format.
Dateobjects contain a
Numberthat represents milliseconds since 1 January 1970 UTC.
A pretty irrelevant to our current use case, but fun fact about the
It should be noted that the maximum
Dateis not of the same value as the maximum safe integer (
Number.MAX_SAFE_INTEGERis 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
Dateobject (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).
Date.now() gives me this:
1603044899619. Not very readable. This is why
Date methods like
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.