;;=============================================================================
;;                    time-to-date
;;=============================================================================
;;AUTOLOAD: time-to-date
;;AUTODOC: time-to-date "transorms number of seconds in readable date"
;; (time-to-date number-of-seconds [timezone-offset])
;;
;; from a time in raw STAT format (seconds GMT since 1/1/1970), returns a list
;; of (year month day hour minute second day-of-the-week-0=sunday) local to
;; the timezone
;; year is year (4 digits)
;; month is 1-12
;; day is 1-31
;; hour is 00-23
;; minute is 00-59
;; day-of-the-week: 0 sunday, 1 = monday, etc...
;; timezone-offset is the number of seconds to add due to timezone. default 
;; to contents of global var TIMEZONE-sec, which is set at 0 on load
;; WARNING: These functions can only handle dates between 1-Jan-1970 (0) and 
;; 19-Jan-2038 (2147483647), sinces seconds are stored in a 32bit integer

(setq days-of-the-week '("Sunday" "Monday" "Tuesday" "Wednesday" "Thursday"
    "Friday" "Saturday"
))

;#define isleap(y) (((y) % 4) == 0 && ((y) % 100) != 0 || ((y) % 400) == 0)
(defmacro isleap (y)
  `(or (and (= 0 (mod ,y 4))
      (/= 0 (mod ,y 100))
    )
    (= 0 (mod ,y 400))
))

(setq time-to-date:leapyear-months '(31 29 31 30 31 30 31 31 30 31 30 31))
(setq time-to-date:year-months '(31 28 31 30 31 30 31 31 30 31 30 31))

(defun time-to-date (seconds &optional (offset TIMEZONE-sec) &aux
    days
    hour
    rem
    min
    wday
    yleap
    (mon 0)
    (year 1970)
    ip
  )
  (if (typep seconds Int) (progn
      (setq days (/ seconds 86400))
      (setq rem (mod seconds 86400))
      (+= rem offset)
      (while (< rem 0)
	(+= rem 86400)
	(-- days)
      )
      (while (>= rem 86400)
	(-= rem 86400)
	(++ days)
      )
      (setq hour (/ rem 3600))
      (setq rem (mod rem 3600))
      (setq min (/ rem 60))
      (setq rem (mod rem 60))
      (setq wday (mod (+ 4 days) 7))
      (if (< wday 0)
	(+= wday 7)
      )
      (if (>= days 0)
	(catch 'Done
	  (while t
	    (setq yleap (isleap year))
	    (if (< days (if yleap 366 365))
	      (throw 'Done)
	    )
	    (++ year)
	    (-= days (if yleap 366 365))
	))
	(catch 'Done
	  (while t
	    (-- year)
	    (setq yleap (isleap year))
	    (+= days (if yleap 366 365))
	    (if (< days 0)
	      (throw 'Done)
      ))))
      
      (setq ip (if yleap time-to-date:leapyear-months
	time-to-date:year-months
      ))

      (while (>= days (getn ip mon))
	(-= days (getn ip mon))
	(++ mon)
      )
      
      (list year (+ mon 1) (+ days 1) hour min rem wday)
)))

;;AUTOLOAD: seconds-to-date
;;AUTODOC: seconds-to-date "alias to time-to-date"

(setq seconds-to-date time-to-date)

;;=============================================================================
;;                    date-to-time
;;=============================================================================
;;AUTOLOAD: date-to-time
;;AUTODOC: date-to-time "transorms date as list of numbers into seconds"
;; (date-to-time (year month day hour minute second))
;;
;; from a date in form: (year month day hour minute second [weekday]),
;; in the local timezone,
;; returns a raw STAT format (seconds GMT since 1/1/1970)
;; year is year (4 digits)
;; month is 1-12
;; day is 1-31
;; hour is 00-23
;; minute is 00-59
;; an optional day-of-the-week element can be added to the end of the list, 
;; allowing date-to-time to be given the output of time-to-date
;; WARNING: These functions can only handle dates between 1-Jan-1970 (0) and 
;; 19-Jan-2038 (2147483647), sinces seconds are stored in a 32bit integer

(defun date-to-time (args &optional weekday &aux
    (year (get args 0 1970))
    (month (- (get args 1 1) 1))
    (day (- (get args 2 1) 1))
    (hour (get args 3 0))
    (minute (get args 4 0))
    (second (get args 5 0))
    yleap
    ym
  )
  (if (< year 1970) 
    (if (< year 70) (+= year 2000) (+= year 1900))
  )
  (if (>= month 12) (progn
      (+= year (/ month 12))
      (setq month (mod month 12))
  ))
  (setq yleap (isleap year))
  (setq ym (if yleap time-to-date:leapyear-months
      time-to-date:year-months))
  (dotimes (m month)
    (setq day (+ day (get ym m 0)))
  )
  (dotimes (y (- year 1970))
    (setq day (+ day (if (isleap (+ 1970 y)) 366 365)))
  )

  (+ (* day 86400) (* hour 3600) (* minute 60) second (- TIMEZONE-sec))
)

;;AUTOLOAD: date-to-seconds
;;AUTODOC: date-to-seconds "alias for date-to-time"

(setq date-to-seconds date-to-time)

;;=============================================================================
;;                    calculates TIMEZONE-sec (in seconds)
;;=============================================================================

;; done once at init

(if (= *machine* 'amiga) (setq TIMEZONE 0))
(if (boundp 'TIMEZONE) (setq TIMEZONE-sec (* 3600 TIMEZONE))) ;back-compat

(if (not (boundp 'TIMEZONE-sec)) (progn
    (with (re-dates
	(regcomp "^([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)")
	date-GMT (String (sh:open "TZ=GMT /bin/date +\"%y %m %d %H %M %S\""))
	date-local (String (sh:open "/bin/date +\"%y %m %d %H %M %S\""))
	date-string-to-seconds (lambda (s) (if (regexec re-dates s)
	    (date-to-seconds (list
		(Int (regsub re-dates 1))
		(Int (regsub re-dates 2))
		(Int (regsub re-dates 3))
		(Int (regsub re-dates 4))
		(Int (regsub re-dates 5))
		(Int (regsub re-dates 6))
	    ))
	    (date-to-seconds '(70 01 01 00 00 00))
	))
      )
      (setq TIMEZONE-sec 0)			;bootstraping
      (setq TIMEZONE-sec (* 60 (/ (+ 30 (- (date-string-to-seconds date-local)
		(date-string-to-seconds date-GMT)
	    )) 60)
    ))					;rounded to the minute
)))
(setq TIMEZONE (/ TIMEZONE-sec 3600))

;;AUTOLOAD: TIMEZONE
;;AUTOLOAD: TIMEZONE-sec
