Datetime
========

My datetime library, with focus on date manipulation in ways sensible for humans. So that a date-time plus one day always keep the time of day. For example, 26 mars 2022 10:00 plus 1 day would give 27 mars 2022 10:00, even though 25 hours have passed due to summer time starting (in Sweden).

Note that while some of these procedures mentions timezones, almost nothing is actually done with it.

Constants
---------

 -- Variable: jan
 -- Variable: january
 -- Variable: feb
 -- Variable: february
 -- Variable: mar
 -- Variable: mars
 -- Variable: apr
 -- Variable: april
 -- Variable: may
 -- Variable: jun
 -- Variable: june
 -- Variable: jul
 -- Variable: july
 -- Variable: aug
 -- Variable: august
 -- Variable: sep
 -- Variable: september
 -- Variable: oct
 -- Variable: october
 -- Variable: nov
 -- Variable: november
 -- Variable: dec
 -- Variable: december
     Numeric constants for all months. JAN = 1, DEC = 12.

 -- Variable: sun
 -- Variable: sunday
 -- Variable: mon
 -- Variable: monday
 -- Variable: tue
 -- Variable: tuesday
 -- Variable: wed
 -- Variable: wednesday
 -- Variable: thu
 -- Variable: thursday
 -- Variable: fri
 -- Variable: friday
 -- Variable: sat
 -- Variable: saturday
     Numeric constants for all weekdays. SUNDAY = 0, SATURDAY = 6.

Parameters and Configuration
----------------------------

 -- parameter: week-start
     Which weekday should be considered the first. Used for calculating week numbers, the start dates of week, and is available for UI-code and the like which wants it.

 -- config: week-start
     Configuration item, updates see week-start.

Datatypes
---------

 -- Immutable Record: <date> year month day
     Object representing a date, without any timezone information. Given the date 2040-03-23 (in ISO-8601 format), YEAR = 2020, MONTH = 3 and DAY = 23.

     Values higher than those usually used are possible, but not recommended.

      -- Function: date? x
          Is X a date object?

      -- Function: date [#:year=0] [#:month=0] [#:day=0]
          Create a new date object.

      -- Function: year <date>
      -- Function: month <date>
      -- Function: day <date>
          Fetch corresponding field from the date object.

 -- Immutable Record: <time> hour minute second
     Object representing a timestamp in a given day, without any timezone information. Given the time 10:20:30, HOUR = 10, MINUTE = 20 and SECOND = 30.

     Values larger than the ``regular'' are allowed, and useful since this type is also used for time offsets.

      -- Function: time? x
          Is X a time object?

      -- Function: time [#:hour=0] [#:minute=0] [#:second=0]
          Create a new time object.

      -- Function: hour <time>
      -- Function: minute <time>
      -- Function: second <time>
          Fetch corresponding field from the time object.

 -- Immutable Record: <datetime> date time tz

     A collation of date and time, along with an optional timezone. Set tz = #f if a timezone is not desired.

      -- Function: datetime? x
          Is X a datetime object?

      -- Function: datetime [#:date] [#:time] [#:tz] [#:year=0] [#:month=0] [#:day=0] [#:hour=0] [#:minute=0] [#:second=0]
          Creates a new <datetime>. If DATE or TIME is given, those are used. Otherwise, a date object is created from YEAR, MONTH and DAY, and time is respectively created from HOUR, MINUTE and SECOND.

      -- Function: get-date
      -- Function: get-timezone
          Note that get-time doesn't exists.

Reader Extensions
-----------------

This module registers reader extensions on #0, #1, and #2. These read either dates, times, or datetimes; using string->date/-time.

Procedures
----------

 -- Function: datetime->unix-time datetime
     Converts DATETIME to an integer representing its unix time.

 -- Function: unix-time->datetime n
     Converts a given unix timestamp to a datetime object. Currently forces the timezone to be UTC.

 -- Function: current-datetime
     Return a datetime object of the current date and time. Currently always returns it in UTC.

 -- Function: current-date
     Get the date component from a call to current-datetime.

 -- Function: get-datetime datetime
     Takes a datetime in any timezone, and renormalize it to local time (as defined by the environment variable TZ). This means that given UTC 10:00 new years day would return 11:00 new years day if ran in sweden.

 -- Function: as-date date/-time
 -- Function: as-time date/-time
     Both procedures takes a <date>, <time>, or <datetime>, and return respectively a <date> or <time> object.

     as-date, when given a time will return a zeroed date object. Vice versa for as-time.

 -- Function: as-datetime date/-time
     Takes a <date>, <time>, or <datetime>, and returns a <datetime> object with the same data, with the (possibly) missing date or time set to all zeroes.

 -- Function: date-zero? date
 -- Function: time-zero? time
     Checks if all components are zero.

 -- Function: leap-year? year
     Given an integer YEAR, return #t if it's a leap year, and #f otherwise.

 -- Function: days-in-month date
     Returns how many days are in the month specified by the <date> DATE. Note that the day component is ignored.

 -- Function: days-in-year date
     Returns how many days are in the year pointed to by DATE.

 -- Function: start-of-month date
     Returns a <date> object equal to date, but with the day component set to 1.

 -- Function: end-of-month date
     Returns a <date> object equal to date, but with the day component set to the last day of the month.

          (end-of-month #2020-01-10)
          ⇒ #2020-01-31
          (end-of-month #2020-02-01)
          ⇒ #2020-02-29

 -- Function: start-of-year date
     Returns a <date> object equal to date, but with the day and month component set to 1.

 -- Function: date-stream date-increment start-day
     Returns an (guile)SRFI-43 stream of <date> objects, starting at START-DAY and stepping in increments of DATE-INCREMENT.

 -- Function: day-stream start-day
     Returns a stream of each day from START-DAY.

 -- Function: month-stream start-day
     Returns a stream of each months from START-DAY. Day component stays the same.

 -- Function: week-stream start-day
     Returns a stream of each week from START-DAY (increments of 7 days).

 -- Function: time-min a b
 -- Function: time-max a b
 -- Function: date-min a b
 -- Function: date-max a b
 -- Function: datetime-min a b
 -- Function: datetime-max a b
     Returns the smaller (or larger) of A or B.

 -- Function: week-day date
     Returns an integer representing the week day of DATE. sunday

 -- Function: week-1-start date [week-start=(week-start)]
     Returns the date which week 1 starts on, according to the (at least) Swedish rule of week counting. week-start

 -- Function: week-number date [week-start=(week-start)]
     Returns the week number of DATE, according to the (at least) Swedish rule of week counting. week-start

 -- Function: date-starting-week week-number date [week-start=(week-start)]
     Returns the first day of week WEEK-NUMBER, DATE is used for year information. week-start

 -- Function: week-day-name week-day [truncate-to] [#:key locale]
     Returns the locale dependent name for the given week day.

     WEEK-DAY is a number per sunday. TRUNCATE-TO may be a number, which limits to the first N letters of the resulting string.

 -- Function: timespan-overlaps? s1-begin s1-end s2-begin s2-end
     Check if the interval S1-BEGIN to S1-END overlaps with the interval S2-BEGIN to S2-END.

 -- Function: find-first-week-day week-day date
     Returns the first instance of the given week-day after DATE.

          (find-first-week-day mon #2020-04-01)
          ⇒ #2020-04-06
          (find-first-week-day mon #2020-04-10)
          ⇒ #2020-04-13
          (find-first-week-day mon #2020-04-30)
          ⇒ #2020-05-04

 -- Function: all-wday-in-month week-day month-date
     Returns instances of the given week-day in month between month-date and end of month.

          (all-wday-in-month mon #2020-06-01)
          ⇒ (#2020-06-01 #2020-06-08 #2020-06-15 #2020-06-22 #2020-06-29)
          (all-wday-in-month mon #2020-06-10)
          ⇒ (#2020-06-15 #2020-06-22 #2020-06-29)

 -- Function: all-wday-in-year week-day year-date
     Returns a list of all instances of WEEK-DAY in YEAR-DATE.

 -- Function: in-date-range? start-date end-date → date → boolean
     Returns a predicate procedure, which checks if a given date is between START-DATE and END-DATE.

 -- Function: weekday-list [week-start=(week-start)]
     Returns a list of the seven week days, with WEEK-START as the beginning of the week.

 -- Function: start-of-week d [week-start=(week-start)]
 -- Function: end-of-week d [week-start=(week-start)]
     Returns the date the week containing D started or ended.

 -- Function: month-days date [week-start=(week-start)]
     Given a month and and which day the week starts on, returns three lists, which are: The days leading up to the current month, but share a week The days in the current month The days after the current month, but which shares a week.

                mars 2020
          må ti on to fr lö sö
                             1
           2  3  4  5  6  7  8
           9 10 11 12 13 14 15
          16 17 18 19 20 21 22
          23 24 25 26 27 28 29
          30 31

          (month-days #2020-03-01 mon)
          ; ⇒ (2020-02-24 ... 2020-02-29)
          ; ⇒ (2020-03-01 ... 2020-03-31)
          ; ⇒ (2020-04-01 ... 2020-04-05)

     Ignores day component of DATE.

 -- Function: days-in-interval start-date end-date
     The amount of days in the given interval, including both endpoints.

 -- Function: year-day date
     Day from start of the year, so 1 feb would be day 32. Also known as Julian day.

 -- Function: time->decimal-hour time
     Convert TIME to a decimal value, so 10:30 would become 10.5.

 -- Function: datetime->decimal-hour dt [start-date]
     Similar to time->decimal-hour, but also looks at the date component.

     START-DATE is required if either the month of year component of DT is non-zero (since months and years have a variable number of hours).

 -- Function: date-range start end [increment=(date day: 1)]
     Returns a list of all dates from start to end. Both inclusive

 -- Function: locale-month
 -- Function: locale-month-short
     These are direct re-exports from (ice-9 i18n)

     See Accessing Locale Information (guile).

 -- Function: date= args ...
 -- Function: date=? args ...
 -- Function: date< args ...
 -- Function: date<? args ...
 -- Function: date> args ...
 -- Function: date>? args ...
 -- Function: date<= args ...
 -- Function: date<=? args ...
 -- Function: date>= args ...
 -- Function: date>=? args ...
     Checks if all date arguments satisfy the predicate.

 -- Function: time= args ...
 -- Function: time=? args ...
 -- Function: time< a b
 -- Function: time<? a b
 -- Function: time> a b
 -- Function: time>? a b
 -- Function: time<= a b
 -- Function: time<=? a b
 -- Function: time>= a b
 -- Function: time>=? a b
     Checks if all time arguments satisfy the predicate.

 -- Function: datetime= args ...
 -- Function: datetime=? args ...
 -- Function: datetime< a b
 -- Function: datetime<? a b
 -- Function: datetime> a b
 -- Function: datetime>? a b
 -- Function: datetime<= a b
 -- Function: datetime<=? a b
 -- Function: datetime>= a b
 -- Function: datetime>=? a b
     Check if all datetime arguments satisfy the predicate. The timezone field is ignored.

 -- Function: date/-time< a b
 -- Function: date/-time<? a b
 -- Function: date/-time> a b
 -- Function: date/-time>? a b
 -- Function: date/-time<= a b
 -- Function: date/-time<=? a b
 -- Function: date/-time>= a b
 -- Function: date/-time>=? a b
     Equivalent to the datetime* versions, but wraps its arguments in as-datetime.

Arithmetic
----------

While only one date (and one time) type is available, it really should be seen as two. Absolute dates, such as the fourth of november, 2022. The other type are intervals, such as 3 years, 4 months and 2 days.

A ``type mismatch'' might therefore lead to some confounding results.

     (date- #2020-01-01 #2020-01-01)
     ⇒ #00-1-11-31
     (date-difference #2020-01-01 #2020-01-01)
     ⇒ #0000-00-00

 -- Function: date+ base rest ...
 -- Function: date- base rest ...
     Add or remove each difference from base.

 -- Function: date-difference end start
     Returns difference between the two dates, in years, months, and days. In such a way that

          (date= (date+ start (date-difference end start)))

 -- Function: time+ base rest ...
 -- Function: time- base rest ...
     Adds (or subtracts) each difference from the base, and returns two values. The sum, and how many midnight's have passed.

          (time+ #22:00:00 (time hour: 4))
          ⇒ #02:00:00
          ⇒ 1

 -- Function: datetime+ base change
 -- Function: datetime- base change

 -- Function: datetime-difference end start

Parsing and Formatting
----------------------

 -- Function: datetime->string datetime [fmt=''~Y-~m-~dT~H:~M:~S''] [#:allow-unknown?]

     Formats DATETIME into a string. The function will throw an error when encountering an unknown format specifier, unless #:ALLOW-UNKNOWN is true.

     ~~
          A literal tilde (~).

     ~H
          Hour, left padded with zeroes to length 2.

     ~k
          Like ~H, but padded with spaces.

     ~M
          Minute, left padded with zeroes to length 2.

     ~S
          Seconds, left padded with zeroes to length 2.

     ~Y
          Year, left padded with zeroes to length 4;

     ~m
          Month number, left padded with zeroes to length 2.

     ~d
          Day in month, left padded with zeroes to length 2.

     ~s
          Epoch time, per UNIX.

     ~e
          Same as ~d, but padded with spaces.

     ~1
          Shorthand for ~Y-~m-~d.

     ~3
          Shorthand for ~H:~M:~S.

     ~A
          Locale week day name.

     ~a
          Locale week day name, truncated to 3 characters.

     ~b
          Locale month name, truncated.

     ~B
          Locale month name, in full.

     ~Z
          Z if the timezone is UTC. Nothing otherwise.

 -- Function: date->string date [fmt=''~Y-~m-~d''] [#:allow-unknown?]
 -- Function: time->string date [fmt=''~H:~M:~S''] [#:allow-unknown?]
     Simple wrappers around datetime->string, which works directly on date or time objects.

 -- Function: string->datetime str [fmt=''~Y-~m-~dT~H:~M:~S~Z''] [locale=%global-locale]
     Attempts to parse STR as a datetime, according to the ruleset FMT. An invalid or unparsable string will throw an error.

     Each token in FMT informs the parser what the next expected token in STR is. If a binding rule is parsed multiple times, then the last one is used for the resulting object. For example,

          (string->datetime "10:20" "~H:~H")
          ⇒ (datetime hour: 20)

     spaces are literal, there is no way to match an arbitrary number of whitespace characters

     ~~
          Next token is a literal tilde.

     ~Z
          If next token is a literal Z then the resulting timezone is set to UTC, otherwise does nothing.

     ~p
          The next token is an AM/PM indicator, matched by the regex ^([AaPp])[.]?[Mm][.]?. A valid token will reinterpret the last hour indicator as 12-hour time (instead of 24 hour time), regardless if its before or after this token.

     ~b
     ~B
     ~h
          Parses a month by name, just as parse-month.

     ~H
     ~M
     ~S
     ~m
     ~d
          Parses up to two digits, but possibly less if a non-digit appears in the string. Then stores the resulting value in either the hour, minute, second, month, or day slot of the resulting object.

          This double function allows both dates without delimiters, such as ``0102'' to be correctly parsed, but also more free form formats, such as ``1 jan''.

     ~Y
          Equivalent to ~H, but reads up to 4 digits, and stores the result in the year field.

 -- Function: parse-month str [locale=%global-locale]
     Returns the first month whose name has STR as its prefix. The result will be on the interval [1, 12], or -1 if no month matched.

 -- Function: string->time str [fmt=''~H:~M:~S''] [locale=%global-locale]
 -- Function: string->date str [fmt=''~Y-~m-~d''] [locale=%global-locale]
     Wrappers around string->datetime, but only returning the time or date component.

 -- Function: string->date/-time string
     Parses string as an ISO-8601 string. Checks for the existence of T, :, or - to determine if it's a datetime, time or date.

 -- Function: parse-ics-date str
 -- Function: parse-ics-time str
 -- Function: parse-ics-datetime str [zone]
     Parses dates per RFC5545.

 -- Function: parse-iso-date str
 -- Function: parse-iso-time str
 -- Function: parse-iso-datetime str
     Parses (the well known subset) of ISO-compatible dates.

 -- Function: parse-freeform-date str
     Currently an alias for parse-iso-datetime, but should preferably be extended.