Time Conversion

Kurt Nørmark ©    normark@cs.auc.dk    Department of Computer Science, Aalborg University, Denmark    

Abstract. This program transforms time from one representation to another. The source representation is the number of seconds elapsed since January 1, 1970, 00:00:00. The target representation is a vector of (year, month, day, hour, minute, second) all normalized in the sense introduced below.

This program is written as a demo program of elucidative programming in connection to the paper An Elucidative Programming Environment for Scheme intended for the Nordic Workshop on Programmming Language Research, NWPER'2000 in Lillehammer, Norway, June 2000.

 

  The solution 
1  Introduction
In this introductory section we first discuss time system formats and a function in Scheme which returns the current time. Then we discuss the issue of normalization and two possible ways to attack the problem.
1.1  Time systems and functions
1.2  The plan of attack
 

Introduction  The plan of attack
1.1  Time systems and functions
There are several different standards for representation of time on a Computer. Universal Time refers to a standard which counts the seconds since January 1, 1900 (midnight, GMT). In this paper we will use another fix point, namely January 1, 1970.

As an introductory observation, it is worthwhile to notice that the number of seconds since January 1, 1970 can is returned by the Scheme function current-time (taking no parameters) in some Scheme systems (SCM, for instance). The Scheme system gets this number from the underlying operating system, of course.

As an example, the current time was 948450887 at the moment of when these words were written. Furthermore

  (time-decode 948450887) = (2000 1 21 10 34 47)
See details about time-decode in the next section. Please be aware of the problems of time zones and daylight saving time, cf. section 3.1

 

Introduction Time systems and functions  
1.2  The plan of attack
We first decide to solve the problem using the functional programming paradigm. This is natural when we program in a Lisp Dialect. But it is also natural because of the problem, as defined above, calls for a function with well-defined input and output (and no side effects). Let us call the overall function
time-decode.

Scheme is weak with respect to static typing so let us here give the signature of the function:

  time-decode: integer -> (year x month x day x hour x minute x second)
The type year represents an integer greater than or equal to 1970, month is the interval [1..12], day is [1..31], hour is [0..23], and both minute and second belong to [0..59]. The vector on the right hand side will be represented as a list in our program.

As a matter of terminology, the integer at the left hand side of the signature will be called the second counter. We say that an integer value is T-normalized if it is contained in the interval of type T. Thus, for instance, the integer 25 is not hour-normalized, but 1 is (leaving us with 24 hours which can be changed to one extra day).

In the one extreme we can imagine the conversions to be done via a (presumably complicated) formula which realizes an efficient calculation of the result, using arithmetic operators. In the other extreme we can imagine a simple solution based on successive counting and subtracting of well-defined time intervals such as years (with and without leap days), months (in four variations), days, and hours from the second counter. This would be a less efficient solution, but if reliability and trust are emphasized, this will provide for a transparent and easy to follow solution.

In the solution presented in section 2 we will allow "counting", but for some simple sub-problems we use solutions based on "calculation" with formulas involving simple arithmetic operators. 

 

 Introduction Post Scriptum 
2  The solution
In this section we describe the solutions in a number of natural subsection. First we find the year. The last information we extract is the month.
2.1  Dealing with years
2.2  Dealing with days, hours, minutes, and seconds
2.3  Dealing with months
2.4  Putting it all together
 

The solution  Dealing with days, hours, minutes, and seconds
2.1  Dealing with years
The first subproblem is to find the year represented by the second counter. We will use the 'count and subtract' approach to find the year. Is is critical that we deal correctly with leap years. Therefore we start our programming with a function predicate
leap-year that determines whether a year is a leap year. The rule is that y is a leap year if it is divisible by 4, it is not a leap year if it is divisible by 100, but - as yet an exception - a leap year if it is divisible by 400. I do not know whether there are more exceptions, but we can be confident that these will not be relevant in our life time, nor in this program's life time. Notice, that the most extreme exception listed above is relevant in this year, 2000.

Now the function years-and-seconds solves the problem. The underlying counting is an iterative process, which we in Scheme handle by a tail-recursive function. This function is called cycle-years. The "cycling" starts from base-year. (cycle-years n y r) maintains the invariant

  n + r = C
where C is the start value of r. n is increased and r is decreased by the the number of seconds in the actual year y. y is increased by one for each iteration. When the rest r becomes less than the seconds in the actual year, we return y and the rest seconds (as a list of two elements).

The function cycle-years depends on the constants seconds-in-a-leap-year and seconds-in-a-normal-year which are easily pre-calculated using a pocket calculator. 

 

The solution Dealing with years Dealing with months
2.2  Dealing with days, hours, minutes, and seconds
Now we have reduced the problem to finding the normalized months, days, hours, minutes and seconds from a rest second counter r that is less than the number of seconds in a year. It would be natural to find the month next, but doing so would call for yet another counting process, because a month is an irregular time interval (some months have 31 days, others 30, February has normally 28 days, but there is 29 days in leap years).

It is easy to find the unnormalized number of days, and the normalized hours, minutes, and seconds from r. This is done by quotient and modulo calculations. The function how-many-days-hours-minutes-seconds does that. We first find the (non day-normalized) number of days by dividing the parameter n by seconds-in-a-day (A link to a program source marker in how-many-days-hours-minutes-seconds). The remainder, called n-rest-1 (A link to a program source marker in how-many-days-hours-minutes-seconds) is used to find the hour-normalized number of hours by division of n-rest-1 by seconds-in-an-hour (A link to a program source marker in how-many-days-hours-minutes-seconds). Again the remainder, n-rest-2 (A link to a program source marker in how-many-days-hours-minutes-seconds) is found, and this quantum is used to find the minute-normalized number of minutes (A link to a program source marker in how-many-days-hours-minutes-seconds). Finally the number of seconds are found in the last modulo calculation (A link to a program source marker in how-many-days-hours-minutes-seconds). We use a sequential name-biding form let* to find the results in a sequential fashion. Still we are entirely within the functional paradigm, of course. The function returns the list of days, hours, minutes, and seconds. 

 

The solution Dealing with days, hours, minutes, and seconds Putting it all together
2.3  Dealing with months
In section
2.2 we almost solved the rest of the problems. However, we still have to find the month component from a non day-normalized number of days. As an example, we may have 45 days, which should represent February 14. As another example, day counter 60 represents February 29 in a leap year, and March 1 in non-leap years. Thus, we clearly see that we need to take the actual year into account when finding the normalized months and days from a day counter.

The function day-and-month solves the problem. Like in section 2.1 we go for a counting solution. This is done by the tail-recursive helping function day-and-month-help. A call of

 (day-and-month-help n m y c)
involves that in year y and month m we are taking
  (days-in-month m y)
days from c and adding a month to m. The function days-in-month relies on a table of month lengths (for a normal year) called month-length-normal-year. The iteration goes on as long as the rest days, c, is less than the number of days in the actual month m (in the year y).

In the function day-and-month we pass the expression (+ 1 day-count) to the formal day counter parameter c in day-and-month-help. The reason is that one day into January brings us to some point in time at January 2, not January 1. Thus, because c will be the day of the month, c can never be 0. In the tail-recursive call, we know that c > (days-in-month m y), thus ensuring this property recursively. 

 

The solution Dealing with months 
2.4  Putting it all together
We have now solved all parts of the problem, and we need to put them together. This is done by the function
time-decode. In a sequential name binding form, let*, we apply years-and-seconds from section 2.1 (A link to a program source marker in time-decode), how-many-days-hours-minutes-seconds from section 2.2 (A link to a program source marker in time-decode), and day-and-month from section 2.3 (A link to a program source marker in time-decode). The hours, minutes, seconds, and days are extracted from the respective return values of helping functions by means of list selectors, which we call first, second, third, and fourth (these function are found in an external library called general.scm). 

 

 The solution  
3  Post Scriptum
In this section we will make a final remark to the time conversion program.
3.1  Final remarks
3.2  title
 

Post Scriptum  title
3.1  Final remarks
As a final remark it may be noticed that time zones and daylight saving time may cause problems for the time conversion. Being in a specific time zone, or going from normal "winter time" to daylight saving time does probably not change the second counter in the computer. Consequently, we need to take corrective actions in the program in order to return the right time from the conversion function .

The easiest counter measure is to add or subtract an hour (3600 seconds) in time-decode just after the parameter n (the second counter) has been passed as parameter.

This concludes the time conversion example.

It would be possible - and natural - to continue the example with the reverse transformation, by calculation of the weekday, and by transformation of time intervals. Most of these can be found in the LAML library called Time, cf. the LAML libraries

 

Post Scriptum Final remarks 
3.2  title
Nu begynder vi at skrive om
time-decode,