Facing the Scary Parts: ‘After Midnight’ Exercise
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.
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 variableMINUTES_PER_HOUR
and assign it to the Number with value60
. 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 variableHOURS_PER_DAY
and assign it to the Number with value24
. Makes sense for…the same reason as above. - On
line 3
, we declare a constant global variableMINUTES_PER_DAY
and assign it to the result of multiplying the values ofHOURS_PER_DAY
andMINUTES_PER_HOUR
. Thus, its value is1440
. Makes sense… - On
lines 5-15
we use a function declaration to define thetimeOfDay
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 reassigndeltaMinutes
to the result ofdeltaMinutes % MINUTES_PER_DAY
. This operator just returns the remainder of dividing the first operand by the second. Thus,deltaMinutes
is now the result of35 % 1440
which is 35. - On
lines 7-9
, we have an if/else condition. If the newdeltaMinutes
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, because35
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:
- 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 thedeltaMinutes
as 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 online 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 variablehours
and assign it to the return value of some math configuration.
> The argument passed toMath.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 is0
. Thus, the hours is0
.
> Which makes sense: we’re only going forward35
minutes which is less than one hour. - On
line 12
, we declare a new local variableminutes
and assign it to the return value ofdeltaMinutes % MINUTES_PER_HOUR
.
> Remember,%
in JavaScript gives us the remainder of that division.35
does not go into60
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 is35
.
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 thepadWithZeroes
function, which accepts two parameters:number
andlength
.
> In our case, inside the function call,number
is0
andlength
is2
.
> Fun fact:padWithZeroes
is available for our use before it’s declared because of hoisting. - On
line 18
, we declare a new local variablenumberString
and assign it to the return value ofString(number)
.
> This returns the converted-to-a-string-version ofnumber
. Thus in our case,numberString
is now assigned to'0'
. - On
lines 20-22
, we use awhile
loop to add our necessary 0’s to thenumberString
. Remember, we currently have'0'
but we want to turn it into'00'
.
> While the length ofnumberString
(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 thepadWithZeroes
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 thepadWithZeroes
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, online 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 newDate
object, we need to supply those details. - On
line 5
we declare a new local variableafterMidnight
and set it to the result of some configuration with theDate
object. Let’s break that down.
>new Date
: we’re creating a newDate
object
>midnight.getTime()
: our starting point is the time from theDate
object created one line above
>+ deltaMinutes * MILLISECONDS_PER_MINUTE
: we add to that midnight starting point thedeltaMinutes
(35 in our case) multiplied by60000
.
> Thus, ourafterMidnight
is assigned to2000–01–01T00:35:00.000Z
. - On
lines 6 & 7
we use theDate
object methodsgetHours()
andgetMinutes()
which do just that, pass them intopadWithZeroes
, 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 aNumber
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 standardDate
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.