PencilCoder

Teacher's Guide: Date Objects!

Overview

Date objects allow students to access the current system time, the computer's internal representation of the passage of time. While access to the Date object can be useful and leads to some interesting coding activities, the primary purpose of introducing it here is to motivate a more formal discussion of the JavaScript Object data type, providing a foundation and segue to the following lesson, Custom(ized) Objects!.

More about the lesson

Date objects

The Date constructor is fairly straightforward to use. The primary complication relates to the inconsistency with the specification of the month value. Unlike the year and day arguments, month is zero-based, meaning January through December are indicated through choice of integers 0 through 11. This is simply an unfortunate quirk of the JavaScript language, apparently tracing back to its roots in the 1990s, as described in this thread in stackoverflow.

The lesson encourages students to look up Date object details using online JavaScript-reference resources. w3Schools.com is a great resource, especially for students new to coding. Mozilla Developer Network (MDN) docs provide more details, appropriate for a more advanced audience. Encourage students to get in the habit of relying on such reference resources when working with predefined classes, as well as to address questions about general features of the language. The habit of turning to online references to augment what they learn in the lessons will become especially valuable in subsequent lessons, as students delve into CSS and all its myriad options.

Students may be disappointed to learn that the Date class does not include methods to return month and day names, such as "August" or "Monday". The closest it comes to this is abbreviations for them within string representations of the Date object. For example, the statement label new Date() yields something along the lines of Thu Aug 22 2019 08:58:20 GMT-0500 (Central Daylight Time). While it is possible to extract the day of week and month-name abbreviations, that requires use of either subscripts or the String class's splice method, topics which will not be explored until much later in the curriculum, in Bracket Notation! and String Manipulation!. Unfortunately, there is no simple way to get actual day or month names without coding it yourself. This will be the focus of an optional activity for the Subscripting! lesson.

Objects more generally

The latest JavaScript standard (as of August 2020) defines eight data types. Seven of these data types are described as primitives: Number, String, Boolean, Null, Undefined, Symbol, and BigInt. (Of these, this curriculum focuses on Number, String, and Boolean.) Primitives share the characteristic of being immutable, meaning these values that cannot be changed after they are created.

The eighth JavaScript data type is Object. Objects are a mutable collection of properties. That is, unlike the other data types, objects can be modified. And, objects conveniently allow multiple properties to be associated directly with a single variable. Object properties are associations between a name and a value. The name (sometimes called a key) is simply a variable name used to reference the value, such as via dot notation. The value can be any data type, including not only primitives (such as Number or String), but also other objects, even including functions (which are a special type of Object).

Students have been working with JavaScript objects since the first lesson in this curriculum, in the form of turtle-related function calls (even though the object itself wasn't explicity described). Later their use of objects became more explicity, through the introduction of multiple sprites and the use of dot-notation. This lesson directs students to work with a built-in JavaScript object. The following lesson, Custom(ized) Objects!, instructs them how to create rudimentary user-defined objects of their own, using Object literal notation (i.e., curly braces).

Notes to activities

The activities in this lesson promote exploration of the Date object, but they also provide reinforcement of recently-learned topics, in particular conditional logic and comparison operators. Some of the activities include significant computational-thinking challenges. Give students the support they need, especially in the form of useful hints (such as are provided below), and ecourage them to continue to be creative as they work on them. For example, a student might make a clock hand gradually change colors as the time changes. Or, she might design a "GoofyClock" that runs counterclockwise.

The instructions for the anolog-clock-inspired activities recommend using sprites to represent the clock hands. It is possible to solve the exercises by clearing the screen each second and redrawing the hands each time, but experience suggests this is more difficult for students to solve.

To successfully orient sprites representing clock hands, students might benefit from the suggestion that they compute angles based on ratios. For example, if the time is 10 minutes after the hour, turn a minuteHand sprite to (10/60)*360 degrees (using a call to turnto). This will yield a minute hand that moves in a step-function fashion. For minute-hand movement that appears more continous, modify the ratio to incorporate the current value for seconds as well.

BetterAnalogClock should track time more accurately for two reasons. First, calls to pause used in the AnalogClock program will only approximately count off each passing second, owing to variations in system processing. While typically not a big concern in the short term, over larger durations, the cumulative error can become significant. A much drawback to keeping track of time using pause is the fact that when the user navigates away from the running program—such as to view another tab in the browser or a different application—the browser will pause execution of the clock program until that tab is reselected.

Note that the DigitalTimeStamp activity intentionally does not prompt students to convert the digital clock to a working digital clock. That challenge will be taken up in the Label Recycling! lesson.

For the ProgramTimer activity, students need to compute the amount of time elapsed between the two Dates. How this is done can be a valuable exploration for students, with respect to problem solving and developing computational thinking skills. A relatively straightforward solution is to convert each date to a number and compare thoe resulting values. Since the programs will run only for a few seconds, we basically need only consider minutes and seconds. For Dates dStart and dEnd, we have:

start = dStart.getMinutes()*1000 + dStart.getMilliseconds()
end = dEnd.getMinutes()*12 + dEnd.getMilliseconds()
elapsed = end - start

This simple algorithm would fail if the hour advanced between dStart and dEnd, but that issue could be easily addressed using conditional logic. However, when comparing Dates differences in time extending to months or even years, the challenges become formidible. One quickly runs into complex calculations relating to the varying number of days in each month and also issues of leap years.

Thankfully, the Date object provides some easy solutions. Its getTime method returns a Number value set to milliseconds elapsed since 1970. We can therefore use them to quickly calculate the time elapsed (in milliseconds) between our two dates:

elapsed  = dEnd.getTime() + dStart.getTime()

But it gets even better. The Date object is defined so that two Date objects can be summed or differenced using the addition and subtraction operators, + and -; thus, there is no need in this case to explicitly invoke the getTime method:

elapsed  = dEnd - dStart

In coding the ProgramTimer activity, students may run into trouble with an issue related to the animation queue issues. In short, a call to await done defer() must preceed instantiation of the second Date object, or else it will be created when the program runs, not at the desired point (e.g., the end of) the animation. This is illustrated in the following example program.

Finally, there is one other solution, though it incorporates so many concepts that are beyond student's current level of understanding that it is included here with minimal explanation only. The Date class itself has a now method, which allows you to access system time to compute the number of milliseconds since 1/1/70 without having to create an instance of Date. Hence, we could code:

start = Date.now()
...
await done defer()
elapsed = Date.now() - start

Additional activities

Two additional activities involving the Date object, Countdown and DigitalClock, appear in the Math, Mod, and More! lesson and the associated notes to that lesson.

Beyond the lesson

Nuances of comparing (Date) objects

Comparison operators accept values of all data types, but, as noted in the notes to the Comparison Operators! lesson, their behavior is more complicated when applied to objects. The results of comparisons depend on the operator in question and (potentially) on how the object is defined.

When passed to >, >=, <, or <=, a data value of type Object is compared based on the value returned by the object's valueOf method. valueOf typically returns a primitive value representation of the object. In the case of Date objects, valueOf is defined to return the same result as the getTime function (introduced above): a numeric value equal to the number of milliseconds since January 1, 1970 Greenwich Mean Time (GMT). This date representation is common in the programming world. Formally referred to as Universal Time Coordinated (UTC), it is more commonly referred to as UNIX time. When comparing two Date objects, d1 > d2 is equivalent to d1.valueOf() > d2.valueOf().

Comparisons of objects involving strict equality or inequality (== and !=) follow a different logic, relating to how Javascript tracks data in the system. (Refer back to the notes to the Variables! lesson for details.) JavaScript tracks data values with memory pointers. (We typically associate such pointers with a variable names, though there need not actually be an explicit name given). In the case of primitive data value (e.g., of type Boolean, Number, or String), a memory pointer directly identifies the location in physical memory (e.g., RAM) where the primitive data is stored. In the case of nonprimitive data values (i.e., of type Object, which includes Date as well as Turtle, Sprite, and myriad others) memory pointers don't identify the underlying data directly, but rather references to other memory locations were the underlying data are stored.

The == and != operators are defined to compare what JavaScript memory pointers identify. With primitives, it's simple: the actual data values get compared. But with objects, the references are what get compared. As a result, == and != determine whether or not the pointers identify the same object in physical memory, rather than whether the two objects are equivalent. Thus, as an example, we get the (initially) somewhat counterintuitive result, that two Date objects that represent the identical calendar date are "not equal":

d1 = new Date(2020,0,1)
d2 = new Date(2020,0,1)
see d1==d2 # false

The upshot is that extra care must be taken when attempting to compare objects for strict equality or inequality. Strategies for testing for equality will typically depend on the specific object in question, i.e., how it was defined. For example, what does it mean for two Turtles to be equal? This is not always an easy question to answer.

Comparing Dates, thankfully, is a simple matter. The recommended approach is to compare the values returned by the getTime method. Continuing with the example above, the two dates can be compared as follows:

see d1.getTime()==d2.getTime() #true

Deprecated methods

Javascript has included a Date object type to access system time since the language was first developed. Over time, Javascript developers realized that some of their original function definitions were less than ideal. For example, they included a getYear function, which returns a two-digit year. This function has been replaced by the function getFullYear, which returns a four-digit year. They kept the original function around, so developer's programs that relied on it would still work with newer versions of Javascript. However, getYear should no longer be used. It is deprecated.

What can go wrong

When constructing dates, someone invariably will enter a two-digit value beginning with 0, such as 02 for March:

birthday = new Date(1973, 02,15)

This yields the syntax error

octal literal '02' must be prefixed with '0o'

This error refers to the fact that you can express numbers in based 8 using octal notaion, e.g. 0o02 represents two ( 2×80 ) and 0o20 represents sixteen ( 2×81 + 0×80 ).

Technicalities

Pre-defined objects, classes, and object literal notation

The differences between the pre-defined object types discussed in this lesson and user-defined objects created using object literal notation—to be introduced in the next lesson—are substantial. Key to bridging that gap is functions. For that reason, topics such as how to define objects with more advanced functionality, such as constructors, as well as related concepts such as object prototypes, classes, and inheritance, will not be addressed until much later in the curriculum, after students have developed sufficient understanding of user-defined functions.

Primitives that (seem to) behave like Objects

Only values of the Object data type consist of properties, and theyfore only such values can be used with dot-notation—whether to access properties stored in that object, including functions, or to add additional properties to an existing object. However, this statement is seemingly contradicted by reguluarly-encountered code. For example, consider the expressions "word".length and "word".toUpperCase(). Both (seemingly) instruct JavaScript to access a property of a String primitive, which should be impossible (primitives don't have properties!). Yet both statements execute, returning values 4 and "WORD", respectively.

This seemingly contradictory behavior owes to wrapper object types that are built-in to JavaScript to facilitate working with primitives (including String, Number, and Boolean). Wrapper objects have constructors with names matching the primitive. For example, x = new Number(5) creates a Number object that will return the primitive value 5 when its valueOf method is called (which often happens implicitly).

When the JavaScript interpreter encounters expressions involving dot-notation applied to primitives, such as "word".length, it replaces the primitive in the expression with a call to the associated wrapper classes's constructor, which then provides the object functionality. For example, "word".length results in the temporarily construction of an object of type String, as if the user had coded new String("word").length.

For the most part, the interpreter's insertion of wrapper objects works seemlessly in the background, but there are exceptions. For example, in the next lesson, students will learn that they can add a property to an object using dot notation, such as in the following code, which adds a property to track a Sprite's color:

o = new Turtle orange
o.color = "orange"
o.dot o.color, 100 #draws an orange dot

Problems are likely to arise when attempting to add a property to a primitive (as opposed to an object) using dot notation, as illustrated by the following snippet:

x = "word"
x.letterCount = 4
see x.letterCount  # undefined

This code runs without error (in particular, the assignment to x.letterCount) yet the property is not stored (as revealed by failed attempt to reference it in the next line). This happens because, when evaluating the second statement, the JavaScript interpreter temporarily creates a wrapper object for the duration of that statement only, as if had been new String(x).lettercount = 4. However, the variable x and the String primitive to which it refers are left unchanged. In fact, when the third line is interpreted, another wrapper object is temporarily created, because x is still a primitive. Thus the code that is actually executed for the third statement is see new Number(x).letterCount. Because the Number object was just instantiated, it has not letterCount property, hence the value returned is undefined.

To make the preceding snippet work as desired, one would have to replace the first line, to explicitly create x as a String object rather than a String primitive. That way, the letterCount property gets added to the object referenced by x, and thus persists:

x = new String("word")
x.letterCount = 4
see x.letterCount #prints 4