Domanda

I am trying to calculate a person's age in Common Lisp using a given date of birth (a string of the form YYYY-MM-DD) but I got the following error:

Error: "2013-12-10"' is not of the expected typenumber'

My code is as follows

(defun current-date-string ()
  "Returns current date as a string."
  (multiple-value-bind (sec min hr day mon yr dow dst-p tz)
                       (get-decoded-time)
    (declare (ignore sec min hr dow dst-p tz))
    (format nil "~A-~A-~A" yr mon day)))

(defvar (dob  1944-01-01))
  (let ((age (- (current-date-string) dob))))

Can anyone give me help in this regard? I suspect that the current date is in string format like my input, but how can we convert it into the same date format as my input?

È stato utile?

Soluzione

Your code

There are two immediate problems:

  1. The subtraction function - takes numbers as arguments. You're passing the result of (current-date-string), and that's a string produced by (format nil "~A-~A-~A" yr mon day).
  2. This code doesn't make any sense:

    (defvar (dob  1944-01-01))
      (let ((age (- (current-date-string) dob))))
    

    It's not indented properly, for one thing. It's two forms, the first of which is (defvar (dob 1944-01-01)). That's not the right syntax for defvar. 1944-01-01 is a symbol, and it's not bound, so even if you had done (defvar dob 1944-01-01), you'd get an error when the definition is evaluated. While (let ((age (- (current-date-string) dob)))) is syntactically correct, you're not doing anything after binding age. You probably want something like (let ((age (- (current-date-string) dob))) <do-something-here>).

Date arithmetic

At any rate, universal times are integer values that are seconds:

25.1.4.2 Universal Time

Universal time is an absolute time represented as a single non-negative integer---the number of seconds since midnight, January 1, 1900 GMT (ignoring leap seconds). Thus the time 1 is 00:00:01 (that is, 12:00:01 a.m.) on January 1, 1900 GMT.

This means that you can take two universal times and subtract one from the other to get the duration in seconds. Unfortunately, Common Lisp doesn't provide functions for manipulating those durations, and it's non-trivial to convert them because of leap years and the like. The Common Lisp Cookbook mentions this:

Dates and Times

Since universal times are simply numbers, they are easier and safer to manipulate than calendar times. Dates and times should always be stored as universal times if possibile, and only converted to string representations for output purposes. For example, it is straightforward to know which of two dates came before the other, by simply comparing the two corresponding universal times with <. Another typical problem is how to compute the "temporal distance" between two given dates. Let's see how to do this with an example: specifically, we will calculate the temporal distance between the first landing on the moon (4:17pm EDT, July 20 1969) and the last takeoff of the space shuttle Challenger (11:38 a.m. EST, January 28, 1986).

* (setq *moon* (encode-universal-time 0 17 16 20 7 1969 4))
2194805820

* (setq *takeoff* (encode-universal-time 0 38 11 28 1 1986 5))
2716303080

* (- *takeoff* *moon*)
521497260

That's a bit over 52 million seconds, corresponding to 6035 days, 20 hours and 21 minutes (you can verify this by dividing that number by 60, 60 and 24 in succession). Going beyond days is a bit harder because months and years don't have a fixed length, but the above is approximately 16 and a half years.

Reading date strings

It sounds like you're needing to also read dates from strings of the form YYYY-MM-DD. If you're confident that the value that you receive will be legal, you can do something as simple as

(defun parse-date-string (date)
  "Read a date string of the form \"YYYY-MM-DD\" and return the 
corresponding universal time."
  (let ((year (parse-integer date :start 0 :end 4))
        (month (parse-integer date :start 5 :end 7))
        (date (parse-integer date :start 8 :end 10)))
    (encode-universal-time 0 0 0 date month year)))

That will return a universal time, and you can do the arithmetic as described above.

Being lazy

It's generally good practice as a programmer to be lazy. In this case, we can be lazy by not implementing these kinds of functions ourselves, but instead using a library that will handle it for us. Some Common Lisp implementations may already provide date and time functions, and there are many time libraries listed on Cliki. I don't know which of these is most widely used, or has the coverage of the kinds of functions that you need, and library recommendations are off-topic for StackOverflow, so you may have to experiment with some to find one that works for you.

Altri suggerimenti

No, can't just subtract one string from another and expect the computer to magically know that these strings are a date and handle them accordingly. Whatever gave you that idea?

Use the library local-time.

Installation with Quicklisp:

(ql:quickload "local-time").

To parse a date:

(local-time:parse-timestring "2013-12-10")

This produces a timestamp object.

To get the current timestamp:

(local-time:now)

To get the difference in seconds:

(local-time:timestamp-difference one-timestamp another-timestamp)

To get the difference in years (rounded down):

(local-time:timestamp-whole-year-difference one-timestamp another-timestamp)
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top