1#
2#
3#            Nim's Runtime Library
4#        (c) Copyright 2018 Nim contributors
5#
6#    See the file "copying.txt", included in this
7#    distribution, for details about the copyright.
8#
9
10##[
11  The `times` module contains routines and types for dealing with time using
12  the `proleptic Gregorian calendar<https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar>`_.
13  It's also available for the
14  `JavaScript target <backends.html#backends-the-javascript-target>`_.
15
16  Although the `times` module supports nanosecond time resolution, the
17  resolution used by `getTime()` depends on the platform and backend
18  (JS is limited to millisecond precision).
19
20  Examples
21  ========
22
23  .. code-block:: nim
24    import std/[times, os]
25    # Simple benchmarking
26    let time = cpuTime()
27    sleep(100) # Replace this with something to be timed
28    echo "Time taken: ", cpuTime() - time
29
30    # Current date & time
31    let now1 = now()     # Current timestamp as a DateTime in local time
32    let now2 = now().utc # Current timestamp as a DateTime in UTC
33    let now3 = getTime() # Current timestamp as a Time
34
35    # Arithmetic using Duration
36    echo "One hour from now      : ", now() + initDuration(hours = 1)
37    # Arithmetic using TimeInterval
38    echo "One year from now      : ", now() + 1.years
39    echo "One month from now     : ", now() + 1.months
40
41  Parsing and Formatting Dates
42  ============================
43
44  The `DateTime` type can be parsed and formatted using the different
45  `parse` and `format` procedures.
46
47  .. code-block:: nim
48
49    let dt = parse("2000-01-01", "yyyy-MM-dd")
50    echo dt.format("yyyy-MM-dd")
51
52  The different format patterns that are supported are documented below.
53
54  ===========  =================================================================================  ==============================================
55  Pattern      Description                                                                        Example
56  ===========  =================================================================================  ==============================================
57  `d`          Numeric value representing the day of the month,                                   | `1/04/2012 -> 1`
58               it will be either one or two digits long.                                          | `21/04/2012 -> 21`
59  `dd`         Same as above, but is always two digits.                                           | `1/04/2012 -> 01`
60                                                                                                  | `21/04/2012 -> 21`
61  `ddd`        Three letter string which indicates the day of the week.                           | `Saturday -> Sat`
62                                                                                                  | `Monday -> Mon`
63  `dddd`       Full string for the day of the week.                                               | `Saturday -> Saturday`
64                                                                                                  | `Monday -> Monday`
65  `h`          The hours in one digit if possible. Ranging from 1-12.                             | `5pm -> 5`
66                                                                                                  | `2am -> 2`
67  `hh`         The hours in two digits always. If the hour is one digit, 0 is prepended.          | `5pm -> 05`
68                                                                                                  | `11am -> 11`
69  `H`          The hours in one digit if possible, ranging from 0-23.                             | `5pm -> 17`
70                                                                                                  | `2am -> 2`
71  `HH`         The hours in two digits always. 0 is prepended if the hour is one digit.           | `5pm -> 17`
72                                                                                                  | `2am -> 02`
73  `m`          The minutes in one digit if possible.                                              | `5:30 -> 30`
74                                                                                                  | `2:01 -> 1`
75  `mm`         Same as above but always two digits, 0 is prepended if the minute is one digit.    | `5:30 -> 30`
76                                                                                                  | `2:01 -> 01`
77  `M`          The month in one digit if possible.                                                | `September -> 9`
78                                                                                                  | `December -> 12`
79  `MM`         The month in two digits always. 0 is prepended if the month value is one digit.    | `September -> 09`
80                                                                                                  | `December -> 12`
81  `MMM`        Abbreviated three-letter form of the month.                                        | `September -> Sep`
82                                                                                                  | `December -> Dec`
83  `MMMM`       Full month string, properly capitalized.                                           | `September -> September`
84  `s`          Seconds as one digit if possible.                                                  | `00:00:06 -> 6`
85  `ss`         Same as above but always two digits. 0 is prepended if the second is one digit.    | `00:00:06 -> 06`
86  `t`          `A` when time is in the AM. `P` when time is in the PM.                            | `5pm -> P`
87                                                                                                  | `2am -> A`
88  `tt`         Same as above, but `AM` and `PM` instead of `A` and `P` respectively.              | `5pm -> PM`
89                                                                                                  | `2am -> AM`
90  `yy`         The last two digits of the year. When parsing, the current century is assumed.     | `2012 AD -> 12`
91  `yyyy`       The year, padded to at least four digits.                                          | `2012 AD -> 2012`
92               Is always positive, even when the year is BC.                                      | `24 AD -> 0024`
93               When the year is more than four digits, '+' is prepended.                          | `24 BC -> 00024`
94                                                                                                  | `12345 AD -> +12345`
95  `YYYY`       The year without any padding.                                                      | `2012 AD -> 2012`
96               Is always positive, even when the year is BC.                                      | `24 AD -> 24`
97                                                                                                  | `24 BC -> 24`
98                                                                                                  | `12345 AD -> 12345`
99  `uuuu`       The year, padded to at least four digits. Will be negative when the year is BC.    | `2012 AD -> 2012`
100               When the year is more than four digits, '+' is prepended unless the year is BC.    | `24 AD -> 0024`
101                                                                                                  | `24 BC -> -0023`
102                                                                                                  | `12345 AD -> +12345`
103  `UUUU`       The year without any padding. Will be negative when the year is BC.                | `2012 AD -> 2012`
104                                                                                                  | `24 AD -> 24`
105                                                                                                  | `24 BC -> -23`
106                                                                                                  | `12345 AD -> 12345`
107  `z`          Displays the timezone offset from UTC.                                             | `UTC+7 -> +7`
108                                                                                                  | `UTC-5 -> -5`
109  `zz`         Same as above but with leading 0.                                                  | `UTC+7 -> +07`
110                                                                                                  | `UTC-5 -> -05`
111  `zzz`        Same as above but with `:mm` where *mm* represents minutes.                        | `UTC+7 -> +07:00`
112                                                                                                  | `UTC-5 -> -05:00`
113  `ZZZ`        Same as above but with `mm` where *mm* represents minutes.                         | `UTC+7 -> +0700`
114                                                                                                  | `UTC-5 -> -0500`
115  `zzzz`       Same as above but with `:ss` where *ss* represents seconds.                        | `UTC+7 -> +07:00:00`
116                                                                                                  | `UTC-5 -> -05:00:00`
117  `ZZZZ`       Same as above but with `ss` where *ss* represents seconds.                         | `UTC+7 -> +070000`
118                                                                                                  | `UTC-5 -> -050000`
119  `g`          Era: AD or BC                                                                      | `300 AD -> AD`
120                                                                                                  | `300 BC -> BC`
121  `fff`        Milliseconds display                                                               | `1000000 nanoseconds -> 1`
122  `ffffff`     Microseconds display                                                               | `1000000 nanoseconds -> 1000`
123  `fffffffff`  Nanoseconds display                                                                | `1000000 nanoseconds -> 1000000`
124  ===========  =================================================================================  ==============================================
125
126  Other strings can be inserted by putting them in `''`. For example
127  `hh'->'mm` will give `01->56`.  The following characters can be
128  inserted without quoting them: `:` `-` `(` `)` `/` `[` `]`
129  `,`. A literal `'` can be specified with `''`.
130
131  However you don't need to necessarily separate format patterns, as an
132  unambiguous format string like `yyyyMMddhhmmss` is also valid (although
133  only for years in the range 1..9999).
134
135  Duration vs TimeInterval
136  ============================
137  The `times` module exports two similar types that are both used to
138  represent some amount of time: `Duration <#Duration>`_ and
139  `TimeInterval <#TimeInterval>`_.
140  This section explains how they differ and when one should be preferred over the
141  other (short answer: use `Duration` unless support for months and years is
142  needed).
143
144  Duration
145  ----------------------------
146  A `Duration` represents a duration of time stored as seconds and
147  nanoseconds. A `Duration` is always fully normalized, so
148  `initDuration(hours = 1)` and `initDuration(minutes = 60)` are equivalent.
149
150  Arithmetic with a `Duration` is very fast, especially when used with the
151  `Time` type, since it only involves basic arithmetic. Because `Duration`
152  is more performant and easier to understand it should generally preferred.
153
154  TimeInterval
155  ----------------------------
156  A `TimeInterval` represents an amount of time expressed in calendar
157  units, for example "1 year and 2 days". Since some units cannot be
158  normalized (the length of a year is different for leap years for example),
159  the `TimeInterval` type uses separate fields for every unit. The
160  `TimeInterval`'s returned from this module generally don't normalize
161  **anything**, so even units that could be normalized (like seconds,
162  milliseconds and so on) are left untouched.
163
164  Arithmetic with a `TimeInterval` can be very slow, because it requires
165  timezone information.
166
167  Since it's slower and more complex, the `TimeInterval` type should be
168  avoided unless the program explicitly needs the features it offers that
169  `Duration` doesn't have.
170
171  How long is a day?
172  ----------------------------
173  It should be especially noted that the handling of days differs between
174  `TimeInterval` and `Duration`. The `Duration` type always treats a day
175  as exactly 86400 seconds. For `TimeInterval`, it's more complex.
176
177  As an example, consider the amount of time between these two timestamps, both
178  in the same timezone:
179
180    - 2018-03-25T12:00+02:00
181    - 2018-03-26T12:00+01:00
182
183  If only the date & time is considered, it appears that exactly one day has
184  passed. However, the UTC offsets are different, which means that the
185  UTC offset was changed somewhere in between. This happens twice each year for
186  timezones that use daylight savings time. Because of this change, the amount
187  of time that has passed is actually 25 hours.
188
189  The `TimeInterval` type uses calendar units, and will say that exactly one
190  day has passed. The `Duration` type on the other hand normalizes everything
191  to seconds, and will therefore say that 90000 seconds has passed, which is
192  the same as 25 hours.
193
194  See also
195  ========
196  * `monotimes module <monotimes.html>`_
197]##
198
199import strutils, math, options
200
201import std/private/since
202include "system/inclrtl"
203
204when defined(js):
205  import jscore
206
207  # This is really bad, but overflow checks are broken badly for
208  # ints on the JS backend. See #6752.
209  {.push overflowChecks: off.}
210  proc `*`(a, b: int64): int64 =
211    system.`*`(a, b)
212  proc `*`(a, b: int): int =
213    system.`*`(a, b)
214  proc `+`(a, b: int64): int64 =
215    system.`+`(a, b)
216  proc `+`(a, b: int): int =
217    system.`+`(a, b)
218  proc `-`(a, b: int64): int64 =
219    system.`-`(a, b)
220  proc `-`(a, b: int): int =
221    system.`-`(a, b)
222  proc inc(a: var int, b: int) =
223    system.inc(a, b)
224  proc inc(a: var int64, b: int) =
225    system.inc(a, b)
226  {.pop.}
227
228elif defined(posix):
229  import posix
230
231  type CTime = posix.Time
232
233  when defined(macosx):
234    proc gettimeofday(tp: var Timeval, unused: pointer = nil)
235      {.importc: "gettimeofday", header: "<sys/time.h>", sideEffect.}
236
237elif defined(windows):
238  import winlean, std/time_t
239
240  type
241    CTime = time_t.Time
242    Tm {.importc: "struct tm", header: "<time.h>", final, pure.} = object
243      tm_sec*: cint   ## Seconds [0,60].
244      tm_min*: cint   ## Minutes [0,59].
245      tm_hour*: cint  ## Hour [0,23].
246      tm_mday*: cint  ## Day of month [1,31].
247      tm_mon*: cint   ## Month of year [0,11].
248      tm_year*: cint  ## Years since 1900.
249      tm_wday*: cint  ## Day of week [0,6] (Sunday =0).
250      tm_yday*: cint  ## Day of year [0,365].
251      tm_isdst*: cint ## Daylight Savings flag.
252
253  proc localtime(a1: var CTime): ptr Tm {.importc, header: "<time.h>", sideEffect.}
254
255type
256  Month* = enum ## Represents a month. Note that the enum starts at `1`,
257                ## so `ord(month)` will give the month number in the
258                ## range `1..12`.
259    mJan = (1, "January")
260    mFeb = "February"
261    mMar = "March"
262    mApr = "April"
263    mMay = "May"
264    mJun = "June"
265    mJul = "July"
266    mAug = "August"
267    mSep = "September"
268    mOct = "October"
269    mNov = "November"
270    mDec = "December"
271
272  WeekDay* = enum ## Represents a weekday.
273    dMon = "Monday"
274    dTue = "Tuesday"
275    dWed = "Wednesday"
276    dThu = "Thursday"
277    dFri = "Friday"
278    dSat = "Saturday"
279    dSun = "Sunday"
280
281type
282  MonthdayRange* = range[1..31]
283  HourRange* = range[0..23]
284  MinuteRange* = range[0..59]
285  SecondRange* = range[0..60] ## \
286    ## Includes the value 60 to allow for a leap second. Note however
287    ## that the `second` of a `DateTime` will never be a leap second.
288  YeardayRange* = range[0..365]
289  NanosecondRange* = range[0..999_999_999]
290
291  Time* = object ## Represents a point in time.
292    seconds: int64
293    nanosecond: NanosecondRange
294
295  DateTime* = object of RootObj  ## \
296    ## Represents a time in different parts. Although this type can represent
297    ## leap seconds, they are generally not supported in this module. They are
298    ## not ignored, but the `DateTime`'s returned by procedures in this
299    ## module will never have a leap second.
300    nanosecond: NanosecondRange
301    second: SecondRange
302    minute: MinuteRange
303    hour: HourRange
304    monthdayZero: int
305    monthZero: int
306    year: int
307    weekday: WeekDay
308    yearday: YeardayRange
309    isDst: bool
310    timezone: Timezone
311    utcOffset: int
312
313  Duration* = object ## Represents a fixed duration of time, meaning a duration
314                     ## that has constant length independent of the context.
315                     ##
316                     ## To create a new `Duration`, use `initDuration
317                     ## <#initDuration,int64,int64,int64,int64,int64,int64,int64,int64>`_.
318                     ## Instead of trying to access the private attributes, use
319                     ## `inSeconds <#inSeconds,Duration>`_ for converting to seconds and
320                     ## `inNanoseconds <#inNanoseconds,Duration>`_ for converting to nanoseconds.
321    seconds: int64
322    nanosecond: NanosecondRange
323
324  TimeUnit* = enum ## Different units of time.
325    Nanoseconds, Microseconds, Milliseconds, Seconds, Minutes, Hours, Days,
326    Weeks, Months, Years
327
328  FixedTimeUnit* = range[Nanoseconds..Weeks] ## \
329      ## Subrange of `TimeUnit` that only includes units of fixed duration.
330      ## These are the units that can be represented by a `Duration`.
331
332  TimeInterval* = object ## \
333      ## Represents a non-fixed duration of time. Can be used to add and
334      ## subtract non-fixed time units from a `DateTime <#DateTime>`_ or
335      ## `Time <#Time>`_.
336      ##
337      ## Create a new `TimeInterval` with `initTimeInterval proc
338      ## <#initTimeInterval,int,int,int,int,int,int,int,int,int,int>`_.
339      ##
340      ## Note that `TimeInterval` doesn't represent a fixed duration of time,
341      ## since the duration of some units depend on the context (e.g a year
342      ## can be either 365 or 366 days long). The non-fixed time units are
343      ## years, months, days and week.
344      ##
345      ## Note that `TimeInterval`'s returned from the `times` module are
346      ## never normalized. If you want to normalize a time unit,
347      ## `Duration <#Duration>`_ should be used instead.
348    nanoseconds*: int    ## The number of nanoseconds
349    microseconds*: int   ## The number of microseconds
350    milliseconds*: int   ## The number of milliseconds
351    seconds*: int        ## The number of seconds
352    minutes*: int        ## The number of minutes
353    hours*: int          ## The number of hours
354    days*: int           ## The number of days
355    weeks*: int          ## The number of weeks
356    months*: int         ## The number of months
357    years*: int          ## The number of years
358
359  Timezone* = ref object ## \
360      ## Timezone interface for supporting `DateTime <#DateTime>`_\s of arbitrary
361      ## timezones. The `times` module only supplies implementations for the
362      ## system's local time and UTC.
363    zonedTimeFromTimeImpl: proc (x: Time): ZonedTime
364        {.tags: [], raises: [], benign.}
365    zonedTimeFromAdjTimeImpl: proc (x: Time): ZonedTime
366        {.tags: [], raises: [], benign.}
367    name: string
368
369  ZonedTime* = object ## Represents a point in time with an associated
370                      ## UTC offset and DST flag. This type is only used for
371                      ## implementing timezones.
372    time*: Time       ## The point in time being represented.
373    utcOffset*: int   ## The offset in seconds west of UTC,
374                      ## including any offset due to DST.
375    isDst*: bool      ## Determines whether DST is in effect.
376
377  DurationParts* = array[FixedTimeUnit, int64] # Array of Duration parts starts
378  TimeIntervalParts* = array[TimeUnit, int] # Array of Duration parts starts
379
380const
381  secondsInMin = 60
382  secondsInHour = 60*60
383  secondsInDay = 60*60*24
384  rateDiff = 10000000'i64 # 100 nsecs
385                          # The number of hectonanoseconds between 1601/01/01 (windows epoch)
386                          # and 1970/01/01 (unix epoch).
387  epochDiff = 116444736000000000'i64
388
389const unitWeights: array[FixedTimeUnit, int64] = [
390  1'i64,
391  1000,
392  1_000_000,
393  1e9.int64,
394  secondsInMin * 1e9.int64,
395  secondsInHour * 1e9.int64,
396  secondsInDay * 1e9.int64,
397  7 * secondsInDay * 1e9.int64,
398]
399
400#
401# Helper procs
402#
403
404{.pragma: operator, rtl, noSideEffect, benign.}
405
406proc convert*[T: SomeInteger](unitFrom, unitTo: FixedTimeUnit, quantity: T): T
407    {.inline.} =
408  ## Convert a quantity of some duration unit to another duration unit.
409  ## This proc only deals with integers, so the result might be truncated.
410  runnableExamples:
411    doAssert convert(Days, Hours, 2) == 48
412    doAssert convert(Days, Weeks, 13) == 1 # Truncated
413    doAssert convert(Seconds, Milliseconds, -1) == -1000
414  if unitFrom < unitTo:
415    (quantity div (unitWeights[unitTo] div unitWeights[unitFrom])).T
416  else:
417    ((unitWeights[unitFrom] div unitWeights[unitTo]) * quantity).T
418
419proc normalize[T: Duration|Time](seconds, nanoseconds: int64): T =
420  ## Normalize a (seconds, nanoseconds) pair and return it as either
421  ## a `Duration` or `Time`. A normalized `Duration|Time` has a
422  ## positive nanosecond part in the range `NanosecondRange`.
423  result.seconds = seconds + convert(Nanoseconds, Seconds, nanoseconds)
424  var nanosecond = nanoseconds mod convert(Seconds, Nanoseconds, 1)
425  if nanosecond < 0:
426    nanosecond += convert(Seconds, Nanoseconds, 1)
427    result.seconds -= 1
428  result.nanosecond = nanosecond.int
429
430proc isLeapYear*(year: int): bool =
431  ## Returns true if `year` is a leap year.
432  runnableExamples:
433    doAssert isLeapYear(2000)
434    doAssert not isLeapYear(1900)
435  year mod 4 == 0 and (year mod 100 != 0 or year mod 400 == 0)
436
437proc getDaysInMonth*(month: Month, year: int): int =
438  ## Get the number of days in `month` of `year`.
439  # http://www.dispersiondesign.com/articles/time/number_of_days_in_a_month
440  runnableExamples:
441    doAssert getDaysInMonth(mFeb, 2000) == 29
442    doAssert getDaysInMonth(mFeb, 2001) == 28
443  case month
444  of mFeb: result = if isLeapYear(year): 29 else: 28
445  of mApr, mJun, mSep, mNov: result = 30
446  else: result = 31
447
448proc assertValidDate(monthday: MonthdayRange, month: Month, year: int)
449    {.inline.} =
450  assert monthday <= getDaysInMonth(month, year),
451    $year & "-" & intToStr(ord(month), 2) & "-" & $monthday &
452      " is not a valid date"
453
454proc toEpochDay(monthday: MonthdayRange, month: Month, year: int): int64 =
455  ## Get the epoch day from a year/month/day date.
456  ## The epoch day is the number of days since 1970/01/01
457  ## (it might be negative).
458  # Based on http://howardhinnant.github.io/date_algorithms.html
459  assertValidDate monthday, month, year
460  var (y, m, d) = (year, ord(month), monthday.int)
461  if m <= 2:
462    y.dec
463
464  let era = (if y >= 0: y else: y-399) div 400
465  let yoe = y - era * 400
466  let doy = (153 * (m + (if m > 2: -3 else: 9)) + 2) div 5 + d-1
467  let doe = yoe * 365 + yoe div 4 - yoe div 100 + doy
468  return era * 146097 + doe - 719468
469
470proc fromEpochDay(epochday: int64):
471    tuple[monthday: MonthdayRange, month: Month, year: int] =
472  ## Get the year/month/day date from a epoch day.
473  ## The epoch day is the number of days since 1970/01/01
474  ## (it might be negative).
475  # Based on http://howardhinnant.github.io/date_algorithms.html
476  var z = epochday
477  z.inc 719468
478  let era = (if z >= 0: z else: z - 146096) div 146097
479  let doe = z - era * 146097
480  let yoe = (doe - doe div 1460 + doe div 36524 - doe div 146096) div 365
481  let y = yoe + era * 400;
482  let doy = doe - (365 * yoe + yoe div 4 - yoe div 100)
483  let mp = (5 * doy + 2) div 153
484  let d = doy - (153 * mp + 2) div 5 + 1
485  let m = mp + (if mp < 10: 3 else: -9)
486  return (d.MonthdayRange, m.Month, (y + ord(m <= 2)).int)
487
488proc getDayOfYear*(monthday: MonthdayRange, month: Month, year: int):
489    YeardayRange {.tags: [], raises: [], benign.} =
490  ## Returns the day of the year.
491  ## Equivalent with `dateTime(year, month, monthday, 0, 0, 0, 0).yearday`.
492  runnableExamples:
493    doAssert getDayOfYear(1, mJan, 2000) == 0
494    doAssert getDayOfYear(10, mJan, 2000) == 9
495    doAssert getDayOfYear(10, mFeb, 2000) == 40
496
497  assertValidDate monthday, month, year
498  const daysUntilMonth: array[Month, int] =
499    [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]
500  const daysUntilMonthLeap: array[Month, int] =
501    [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335]
502
503  if isLeapYear(year):
504    result = daysUntilMonthLeap[month] + monthday - 1
505  else:
506    result = daysUntilMonth[month] + monthday - 1
507
508proc getDayOfWeek*(monthday: MonthdayRange, month: Month, year: int): WeekDay
509    {.tags: [], raises: [], benign.} =
510  ## Returns the day of the week enum from day, month and year.
511  ## Equivalent with `dateTime(year, month, monthday, 0, 0, 0, 0).weekday`.
512  runnableExamples:
513    doAssert getDayOfWeek(13, mJun, 1990) == dWed
514    doAssert $getDayOfWeek(13, mJun, 1990) == "Wednesday"
515
516  assertValidDate monthday, month, year
517  # 1970-01-01 is a Thursday, we adjust to the previous Monday
518  let days = toEpochDay(monthday, month, year) - 3
519  let weeks = floorDiv(days, 7)
520  let wd = days - weeks * 7
521  # The value of d is 0 for a Sunday, 1 for a Monday, 2 for a Tuesday, etc.
522  # so we must correct for the WeekDay type.
523  result = if wd == 0: dSun else: WeekDay(wd - 1)
524
525proc getDaysInYear*(year: int): int =
526  ## Get the number of days in a `year`
527  runnableExamples:
528    doAssert getDaysInYear(2000) == 366
529    doAssert getDaysInYear(2001) == 365
530  result = 365 + (if isLeapYear(year): 1 else: 0)
531
532proc stringifyUnit(value: int | int64, unit: TimeUnit): string =
533  ## Stringify time unit with it's name, lowercased
534  let strUnit = $unit
535  result = ""
536  result.add($value)
537  result.add(" ")
538  if abs(value) != 1:
539    result.add(strUnit.toLowerAscii())
540  else:
541    result.add(strUnit[0..^2].toLowerAscii())
542
543proc humanizeParts(parts: seq[string]): string =
544  ## Make date string parts human-readable
545  result = ""
546  if parts.len == 0:
547    result.add "0 nanoseconds"
548  elif parts.len == 1:
549    result = parts[0]
550  elif parts.len == 2:
551    result = parts[0] & " and " & parts[1]
552  else:
553    for i in 0..high(parts)-1:
554      result.add parts[i] & ", "
555    result.add "and " & parts[high(parts)]
556
557template subImpl[T: Duration|Time](a: Duration|Time, b: Duration|Time): T =
558  normalize[T](a.seconds - b.seconds, a.nanosecond - b.nanosecond)
559
560template addImpl[T: Duration|Time](a: Duration|Time, b: Duration|Time): T =
561  normalize[T](a.seconds + b.seconds, a.nanosecond + b.nanosecond)
562
563template ltImpl(a: Duration|Time, b: Duration|Time): bool =
564  a.seconds < b.seconds or (
565    a.seconds == b.seconds and a.nanosecond < b.nanosecond)
566
567template lqImpl(a: Duration|Time, b: Duration|Time): bool =
568  a.seconds < b.seconds or (
569    a.seconds == b.seconds and a.nanosecond <= b.nanosecond)
570
571template eqImpl(a: Duration|Time, b: Duration|Time): bool =
572  a.seconds == b.seconds and a.nanosecond == b.nanosecond
573
574#
575# Duration
576#
577
578const DurationZero* = Duration() ## \
579  ## Zero value for durations. Useful for comparisons.
580  ##
581  ## .. code-block:: nim
582  ##
583  ##   doAssert initDuration(seconds = 1) > DurationZero
584  ##   doAssert initDuration(seconds = 0) == DurationZero
585
586proc initDuration*(nanoseconds, microseconds, milliseconds,
587                   seconds, minutes, hours, days, weeks: int64 = 0): Duration =
588  ## Create a new `Duration <#Duration>`_.
589  runnableExamples:
590    let dur = initDuration(seconds = 1, milliseconds = 1)
591    doAssert dur.inMilliseconds == 1001
592    doAssert dur.inSeconds == 1
593
594  let seconds = convert(Weeks, Seconds, weeks) +
595    convert(Days, Seconds, days) +
596    convert(Minutes, Seconds, minutes) +
597    convert(Hours, Seconds, hours) +
598    convert(Seconds, Seconds, seconds) +
599    convert(Milliseconds, Seconds, milliseconds) +
600    convert(Microseconds, Seconds, microseconds) +
601    convert(Nanoseconds, Seconds, nanoseconds)
602  let nanoseconds = (convert(Milliseconds, Nanoseconds, milliseconds mod 1000) +
603    convert(Microseconds, Nanoseconds, microseconds mod 1_000_000) +
604    nanoseconds mod 1_000_000_000).int
605  # Nanoseconds might be negative so we must normalize.
606  result = normalize[Duration](seconds, nanoseconds)
607
608template convert(dur: Duration, unit: static[FixedTimeUnit]): int64 =
609  # The correction is required due to how durations are normalized.
610  # For example,` initDuration(nanoseconds = -1)` is stored as
611  # { seconds = -1, nanoseconds = 999999999 }.
612  when unit == Nanoseconds:
613    dur.seconds * 1_000_000_000 + dur.nanosecond
614  else:
615    let correction = dur.seconds < 0 and dur.nanosecond > 0
616    when unit >= Seconds:
617      convert(Seconds, unit, dur.seconds + ord(correction))
618    else:
619      if correction:
620        convert(Seconds, unit, dur.seconds + 1) -
621          convert(Nanoseconds, unit,
622            convert(Seconds, Nanoseconds, 1) - dur.nanosecond)
623      else:
624        convert(Seconds, unit, dur.seconds) +
625          convert(Nanoseconds, unit, dur.nanosecond)
626
627proc inWeeks*(dur: Duration): int64 =
628  ## Converts the duration to the number of whole weeks.
629  runnableExamples:
630    let dur = initDuration(days = 8)
631    doAssert dur.inWeeks == 1
632  dur.convert(Weeks)
633
634proc inDays*(dur: Duration): int64 =
635  ## Converts the duration to the number of whole days.
636  runnableExamples:
637    let dur = initDuration(hours = -50)
638    doAssert dur.inDays == -2
639  dur.convert(Days)
640
641proc inHours*(dur: Duration): int64 =
642  ## Converts the duration to the number of whole hours.
643  runnableExamples:
644    let dur = initDuration(minutes = 60, days = 2)
645    doAssert dur.inHours == 49
646  dur.convert(Hours)
647
648proc inMinutes*(dur: Duration): int64 =
649  ## Converts the duration to the number of whole minutes.
650  runnableExamples:
651    let dur = initDuration(hours = 2, seconds = 10)
652    doAssert dur.inMinutes == 120
653  dur.convert(Minutes)
654
655proc inSeconds*(dur: Duration): int64 =
656  ## Converts the duration to the number of whole seconds.
657  runnableExamples:
658    let dur = initDuration(hours = 2, milliseconds = 10)
659    doAssert dur.inSeconds == 2 * 60 * 60
660  dur.convert(Seconds)
661
662proc inMilliseconds*(dur: Duration): int64 =
663  ## Converts the duration to the number of whole milliseconds.
664  runnableExamples:
665    let dur = initDuration(seconds = -2)
666    doAssert dur.inMilliseconds == -2000
667  dur.convert(Milliseconds)
668
669proc inMicroseconds*(dur: Duration): int64 =
670  ## Converts the duration to the number of whole microseconds.
671  runnableExamples:
672    let dur = initDuration(seconds = -2)
673    doAssert dur.inMicroseconds == -2000000
674  dur.convert(Microseconds)
675
676proc inNanoseconds*(dur: Duration): int64 =
677  ## Converts the duration to the number of whole nanoseconds.
678  runnableExamples:
679    let dur = initDuration(seconds = -2)
680    doAssert dur.inNanoseconds == -2000000000
681  dur.convert(Nanoseconds)
682
683proc toParts*(dur: Duration): DurationParts =
684  ## Converts a duration into an array consisting of fixed time units.
685  ##
686  ## Each value in the array gives information about a specific unit of
687  ## time, for example `result[Days]` gives a count of days.
688  ##
689  ## This procedure is useful for converting `Duration` values to strings.
690  runnableExamples:
691    var dp = toParts(initDuration(weeks = 2, days = 1))
692    doAssert dp[Days] == 1
693    doAssert dp[Weeks] == 2
694    doAssert dp[Minutes] == 0
695    dp = toParts(initDuration(days = -1))
696    doAssert dp[Days] == -1
697
698  var remS = dur.seconds
699  var remNs = dur.nanosecond.int
700
701  # Ensure the same sign for seconds and nanoseconds
702  if remS < 0 and remNs != 0:
703    remNs -= convert(Seconds, Nanoseconds, 1)
704    remS.inc 1
705
706  for unit in countdown(Weeks, Seconds):
707    let quantity = convert(Seconds, unit, remS)
708    remS = remS mod convert(unit, Seconds, 1)
709
710    result[unit] = quantity
711
712  for unit in countdown(Milliseconds, Nanoseconds):
713    let quantity = convert(Nanoseconds, unit, remNs)
714    remNs = remNs mod convert(unit, Nanoseconds, 1)
715
716    result[unit] = quantity
717
718proc `$`*(dur: Duration): string =
719  ## Human friendly string representation of a `Duration`.
720  runnableExamples:
721    doAssert $initDuration(seconds = 2) == "2 seconds"
722    doAssert $initDuration(weeks = 1, days = 2) == "1 week and 2 days"
723    doAssert $initDuration(hours = 1, minutes = 2, seconds = 3) ==
724      "1 hour, 2 minutes, and 3 seconds"
725    doAssert $initDuration(milliseconds = -1500) ==
726      "-1 second and -500 milliseconds"
727  var parts = newSeq[string]()
728  var numParts = toParts(dur)
729
730  for unit in countdown(Weeks, Nanoseconds):
731    let quantity = numParts[unit]
732    if quantity != 0.int64:
733      parts.add(stringifyUnit(quantity, unit))
734
735  result = humanizeParts(parts)
736
737proc `+`*(a, b: Duration): Duration {.operator, extern: "ntAddDuration".} =
738  ## Add two durations together.
739  runnableExamples:
740    doAssert initDuration(seconds = 1) + initDuration(days = 1) ==
741      initDuration(seconds = 1, days = 1)
742  addImpl[Duration](a, b)
743
744proc `-`*(a, b: Duration): Duration {.operator, extern: "ntSubDuration".} =
745  ## Subtract a duration from another.
746  runnableExamples:
747    doAssert initDuration(seconds = 1, days = 1) - initDuration(seconds = 1) ==
748      initDuration(days = 1)
749  subImpl[Duration](a, b)
750
751proc `-`*(a: Duration): Duration {.operator, extern: "ntReverseDuration".} =
752  ## Reverse a duration.
753  runnableExamples:
754    doAssert -initDuration(seconds = 1) == initDuration(seconds = -1)
755  normalize[Duration](-a.seconds, -a.nanosecond)
756
757proc `<`*(a, b: Duration): bool {.operator, extern: "ntLtDuration".} =
758  ## Note that a duration can be negative,
759  ## so even if `a < b` is true `a` might
760  ## represent a larger absolute duration.
761  ## Use `abs(a) < abs(b)` to compare the absolute
762  ## duration.
763  runnableExamples:
764    doAssert initDuration(seconds = 1) < initDuration(seconds = 2)
765    doAssert initDuration(seconds = -2) < initDuration(seconds = 1)
766    doAssert initDuration(seconds = -2).abs < initDuration(seconds = 1).abs == false
767  ltImpl(a, b)
768
769proc `<=`*(a, b: Duration): bool {.operator, extern: "ntLeDuration".} =
770  lqImpl(a, b)
771
772proc `==`*(a, b: Duration): bool {.operator, extern: "ntEqDuration".} =
773  runnableExamples:
774    let
775      d1 = initDuration(weeks = 1)
776      d2 = initDuration(days = 7)
777    doAssert d1 == d2
778  eqImpl(a, b)
779
780proc `*`*(a: int64, b: Duration): Duration {.operator,
781    extern: "ntMulInt64Duration".} =
782  ## Multiply a duration by some scalar.
783  runnableExamples:
784    doAssert 5 * initDuration(seconds = 1) == initDuration(seconds = 5)
785    doAssert 3 * initDuration(minutes = 45) == initDuration(hours = 2, minutes = 15)
786  normalize[Duration](a * b.seconds, a * b.nanosecond)
787
788proc `*`*(a: Duration, b: int64): Duration {.operator,
789    extern: "ntMulDuration".} =
790  ## Multiply a duration by some scalar.
791  runnableExamples:
792    doAssert initDuration(seconds = 1) * 5 == initDuration(seconds = 5)
793    doAssert initDuration(minutes = 45) * 3 == initDuration(hours = 2, minutes = 15)
794  b * a
795
796proc `+=`*(d1: var Duration, d2: Duration) =
797  d1 = d1 + d2
798
799proc `-=`*(dt: var Duration, ti: Duration) =
800  dt = dt - ti
801
802proc `*=`*(a: var Duration, b: int) =
803  a = a * b
804
805proc `div`*(a: Duration, b: int64): Duration {.operator,
806    extern: "ntDivDuration".} =
807  ## Integer division for durations.
808  runnableExamples:
809    doAssert initDuration(seconds = 3) div 2 ==
810      initDuration(milliseconds = 1500)
811    doAssert initDuration(minutes = 45) div 30 ==
812      initDuration(minutes = 1, seconds = 30)
813    doAssert initDuration(nanoseconds = 3) div 2 ==
814      initDuration(nanoseconds = 1)
815  let carryOver = convert(Seconds, Nanoseconds, a.seconds mod b)
816  normalize[Duration](a.seconds div b, (a.nanosecond + carryOver) div b)
817
818proc high*(typ: typedesc[Duration]): Duration =
819  ## Get the longest representable duration.
820  initDuration(seconds = high(int64), nanoseconds = high(NanosecondRange))
821
822proc low*(typ: typedesc[Duration]): Duration =
823  ## Get the longest representable duration of negative direction.
824  initDuration(seconds = low(int64))
825
826proc abs*(a: Duration): Duration =
827  runnableExamples:
828    doAssert initDuration(milliseconds = -1500).abs ==
829      initDuration(milliseconds = 1500)
830  initDuration(seconds = abs(a.seconds), nanoseconds = -a.nanosecond)
831
832#
833# Time
834#
835
836proc initTime*(unix: int64, nanosecond: NanosecondRange): Time =
837  ## Create a `Time <#Time>`_ from a unix timestamp and a nanosecond part.
838  result.seconds = unix
839  result.nanosecond = nanosecond
840
841proc nanosecond*(time: Time): NanosecondRange =
842  ## Get the fractional part of a `Time` as the number
843  ## of nanoseconds of the second.
844  time.nanosecond
845
846proc fromUnix*(unix: int64): Time
847    {.benign, tags: [], raises: [], noSideEffect.} =
848  ## Convert a unix timestamp (seconds since `1970-01-01T00:00:00Z`)
849  ## to a `Time`.
850  runnableExamples:
851    doAssert $fromUnix(0).utc == "1970-01-01T00:00:00Z"
852  initTime(unix, 0)
853
854proc toUnix*(t: Time): int64 {.benign, tags: [], raises: [], noSideEffect.} =
855  ## Convert `t` to a unix timestamp (seconds since `1970-01-01T00:00:00Z`).
856  ## See also `toUnixFloat` for subsecond resolution.
857  runnableExamples:
858    doAssert fromUnix(0).toUnix() == 0
859  t.seconds
860
861proc fromUnixFloat(seconds: float): Time {.benign, tags: [], raises: [], noSideEffect.} =
862  ## Convert a unix timestamp in seconds to a `Time`; same as `fromUnix`
863  ## but with subsecond resolution.
864  runnableExamples:
865    doAssert fromUnixFloat(123456.0) == fromUnixFloat(123456)
866    doAssert fromUnixFloat(-123456.0) == fromUnixFloat(-123456)
867  let secs = seconds.floor
868  let nsecs = (seconds - secs) * 1e9
869  initTime(secs.int64, nsecs.NanosecondRange)
870
871proc toUnixFloat(t: Time): float {.benign, tags: [], raises: [].} =
872  ## Same as `toUnix` but using subsecond resolution.
873  runnableExamples:
874    let t = getTime()
875    # `<` because of rounding errors
876    doAssert abs(t.toUnixFloat().fromUnixFloat - t) < initDuration(nanoseconds = 1000)
877  t.seconds.float + t.nanosecond / convert(Seconds, Nanoseconds, 1)
878
879since((1, 1)):
880  export fromUnixFloat
881  export toUnixFloat
882
883
884proc fromWinTime*(win: int64): Time =
885  ## Convert a Windows file time (100-nanosecond intervals since
886  ## `1601-01-01T00:00:00Z`) to a `Time`.
887  const hnsecsPerSec = convert(Seconds, Nanoseconds, 1) div 100
888  let nanos = floorMod(win, hnsecsPerSec) * 100
889  let seconds = floorDiv(win - epochDiff, hnsecsPerSec)
890  result = initTime(seconds, nanos)
891
892proc toWinTime*(t: Time): int64 =
893  ## Convert `t` to a Windows file time (100-nanosecond intervals
894  ## since `1601-01-01T00:00:00Z`).
895  result = t.seconds * rateDiff + epochDiff + t.nanosecond div 100
896
897proc getTime*(): Time {.tags: [TimeEffect], benign.} =
898  ## Gets the current time as a `Time` with up to nanosecond resolution.
899  when defined(js):
900    let millis = newDate().getTime()
901    let seconds = convert(Milliseconds, Seconds, millis)
902    let nanos = convert(Milliseconds, Nanoseconds,
903      millis mod convert(Seconds, Milliseconds, 1).int)
904    result = initTime(seconds, nanos)
905  elif defined(macosx):
906    var a {.noinit.}: Timeval
907    gettimeofday(a)
908    result = initTime(a.tv_sec.int64,
909                      convert(Microseconds, Nanoseconds, a.tv_usec.int))
910  elif defined(posix):
911    var ts {.noinit.}: Timespec
912    discard clock_gettime(CLOCK_REALTIME, ts)
913    result = initTime(ts.tv_sec.int64, ts.tv_nsec.int)
914  elif defined(windows):
915    var f {.noinit.}: FILETIME
916    getSystemTimeAsFileTime(f)
917    result = fromWinTime(rdFileTime(f))
918
919proc `-`*(a, b: Time): Duration {.operator, extern: "ntDiffTime".} =
920  ## Computes the duration between two points in time.
921  runnableExamples:
922    doAssert initTime(1000, 100) - initTime(500, 20) ==
923      initDuration(minutes = 8, seconds = 20, nanoseconds = 80)
924  subImpl[Duration](a, b)
925
926proc `+`*(a: Time, b: Duration): Time {.operator, extern: "ntAddTime".} =
927  ## Add a duration of time to a `Time`.
928  runnableExamples:
929    doAssert (fromUnix(0) + initDuration(seconds = 1)) == fromUnix(1)
930  addImpl[Time](a, b)
931
932proc `-`*(a: Time, b: Duration): Time {.operator, extern: "ntSubTime".} =
933  ## Subtracts a duration of time from a `Time`.
934  runnableExamples:
935    doAssert (fromUnix(0) - initDuration(seconds = 1)) == fromUnix(-1)
936  subImpl[Time](a, b)
937
938proc `<`*(a, b: Time): bool {.operator, extern: "ntLtTime".} =
939  ## Returns true if `a < b`, that is if `a` happened before `b`.
940  runnableExamples:
941    doAssert initTime(50, 0) < initTime(99, 0)
942  ltImpl(a, b)
943
944proc `<=`*(a, b: Time): bool {.operator, extern: "ntLeTime".} =
945  ## Returns true if `a <= b`.
946  lqImpl(a, b)
947
948proc `==`*(a, b: Time): bool {.operator, extern: "ntEqTime".} =
949  ## Returns true if `a == b`, that is if both times represent the same point in time.
950  eqImpl(a, b)
951
952proc `+=`*(t: var Time, b: Duration) =
953  t = t + b
954
955proc `-=`*(t: var Time, b: Duration) =
956  t = t - b
957
958proc high*(typ: typedesc[Time]): Time =
959  initTime(high(int64), high(NanosecondRange))
960
961proc low*(typ: typedesc[Time]): Time =
962  initTime(low(int64), 0)
963
964#
965# DateTime & Timezone
966#
967
968template assertDateTimeInitialized(dt: DateTime) =
969  assert dt.monthdayZero != 0, "Uninitialized datetime"
970
971proc nanosecond*(dt: DateTime): NanosecondRange {.inline.} =
972  ## The number of nanoseconds after the second,
973  ## in the range 0 to 999_999_999.
974  assertDateTimeInitialized(dt)
975  dt.nanosecond
976
977proc second*(dt: DateTime): SecondRange {.inline.} =
978  ## The number of seconds after the minute,
979  ## in the range 0 to 59.
980  assertDateTimeInitialized(dt)
981  dt.second
982
983proc minute*(dt: DateTime): MinuteRange {.inline.} =
984  ## The number of minutes after the hour,
985  ## in the range 0 to 59.
986  assertDateTimeInitialized(dt)
987  dt.minute
988
989proc hour*(dt: DateTime): HourRange {.inline.} =
990  ## The number of hours past midnight,
991  ## in the range 0 to 23.
992  assertDateTimeInitialized(dt)
993  dt.hour
994
995proc monthday*(dt: DateTime): MonthdayRange {.inline.} =
996  ## The day of the month, in the range 1 to 31.
997  assertDateTimeInitialized(dt)
998  # 'cast' to avoid extra range check
999  cast[MonthdayRange](dt.monthdayZero)
1000
1001proc month*(dt: DateTime): Month =
1002  ## The month as an enum, the ordinal value
1003  ## is in the range 1 to 12.
1004  assertDateTimeInitialized(dt)
1005  # 'cast' to avoid extra range check
1006  cast[Month](dt.monthZero)
1007
1008proc year*(dt: DateTime): int {.inline.} =
1009  ## The year, using astronomical year numbering
1010  ## (meaning that before year 1 is year 0,
1011  ## then year -1 and so on).
1012  assertDateTimeInitialized(dt)
1013  dt.year
1014
1015proc weekday*(dt: DateTime): WeekDay {.inline.} =
1016  ## The day of the week as an enum, the ordinal
1017  ## value is in the range 0 (monday) to 6 (sunday).
1018  assertDateTimeInitialized(dt)
1019  dt.weekday
1020
1021proc yearday*(dt: DateTime): YeardayRange {.inline.} =
1022  ## The number of days since January 1,
1023  ## in the range 0 to 365.
1024  assertDateTimeInitialized(dt)
1025  dt.yearday
1026
1027proc isDst*(dt: DateTime): bool {.inline.} =
1028  ## Determines whether DST is in effect.
1029  ## Always false for the JavaScript backend.
1030  assertDateTimeInitialized(dt)
1031  dt.isDst
1032
1033proc timezone*(dt: DateTime): Timezone {.inline.} =
1034  ## The timezone represented as an implementation
1035  ## of `Timezone`.
1036  assertDateTimeInitialized(dt)
1037  dt.timezone
1038
1039proc utcOffset*(dt: DateTime): int {.inline.} =
1040  ## The offset in seconds west of UTC, including
1041  ## any offset due to DST. Note that the sign of
1042  ## this number is the opposite of the one in a
1043  ## formatted offset string like `+01:00` (which
1044  ## would be equivalent to the UTC offset
1045  ## `-3600`).
1046  assertDateTimeInitialized(dt)
1047  dt.utcOffset
1048
1049proc isInitialized(dt: DateTime): bool =
1050  # Returns true if `dt` is not the (invalid) default value for `DateTime`.
1051  runnableExamples:
1052    doAssert now().isInitialized
1053    doAssert not default(DateTime).isInitialized
1054  dt.monthZero != 0
1055
1056since((1, 3)):
1057  export isInitialized
1058
1059proc isLeapDay*(dt: DateTime): bool {.since: (1, 1).} =
1060  ## Returns whether `t` is a leap day, i.e. Feb 29 in a leap year. This matters
1061  ## as it affects time offset calculations.
1062  runnableExamples:
1063    let dt = dateTime(2020, mFeb, 29, 00, 00, 00, 00, utc())
1064    doAssert dt.isLeapDay
1065    doAssert dt+1.years-1.years != dt
1066    let dt2 = dateTime(2020, mFeb, 28, 00, 00, 00, 00, utc())
1067    doAssert not dt2.isLeapDay
1068    doAssert dt2+1.years-1.years == dt2
1069    doAssertRaises(Exception): discard dateTime(2021, mFeb, 29, 00, 00, 00, 00, utc())
1070  assertDateTimeInitialized dt
1071  dt.year.isLeapYear and dt.month == mFeb and dt.monthday == 29
1072
1073proc toTime*(dt: DateTime): Time {.tags: [], raises: [], benign.} =
1074  ## Converts a `DateTime` to a `Time` representing the same point in time.
1075  assertDateTimeInitialized dt
1076  let epochDay = toEpochDay(dt.monthday, dt.month, dt.year)
1077  var seconds = epochDay * secondsInDay
1078  seconds.inc dt.hour * secondsInHour
1079  seconds.inc dt.minute * 60
1080  seconds.inc dt.second
1081  seconds.inc dt.utcOffset
1082  result = initTime(seconds, dt.nanosecond)
1083
1084proc initDateTime(zt: ZonedTime, zone: Timezone): DateTime =
1085  ## Create a new `DateTime` using `ZonedTime` in the specified timezone.
1086  let adjTime = zt.time - initDuration(seconds = zt.utcOffset)
1087  let s = adjTime.seconds
1088  let epochday = floorDiv(s, secondsInDay)
1089  var rem = s - epochday * secondsInDay
1090  let hour = rem div secondsInHour
1091  rem = rem - hour * secondsInHour
1092  let minute = rem div secondsInMin
1093  rem = rem - minute * secondsInMin
1094  let second = rem
1095
1096  let (d, m, y) = fromEpochDay(epochday)
1097
1098  DateTime(
1099    year: y,
1100    monthZero: m.int,
1101    monthdayZero: d,
1102    hour: hour,
1103    minute: minute,
1104    second: second,
1105    nanosecond: zt.time.nanosecond,
1106    weekday: getDayOfWeek(d, m, y),
1107    yearday: getDayOfYear(d, m, y),
1108    isDst: zt.isDst,
1109    timezone: zone,
1110    utcOffset: zt.utcOffset
1111  )
1112
1113proc newTimezone*(
1114      name: string,
1115      zonedTimeFromTimeImpl: proc (time: Time): ZonedTime
1116          {.tags: [], raises: [], benign.},
1117      zonedTimeFromAdjTimeImpl: proc (adjTime: Time): ZonedTime
1118          {.tags: [], raises: [], benign.}
1119    ): owned Timezone =
1120  ## Create a new `Timezone`.
1121  ##
1122  ## `zonedTimeFromTimeImpl` and `zonedTimeFromAdjTimeImpl` is used
1123  ## as the underlying implementations for `zonedTimeFromTime` and
1124  ## `zonedTimeFromAdjTime`.
1125  ##
1126  ## If possible, the name parameter should match the name used in the
1127  ## tz database. If the timezone doesn't exist in the tz database, or if the
1128  ## timezone name is unknown, then any string that describes the timezone
1129  ## unambiguously can be used. Note that the timezones name is used for
1130  ## checking equality!
1131  runnableExamples:
1132    proc utcTzInfo(time: Time): ZonedTime =
1133      ZonedTime(utcOffset: 0, isDst: false, time: time)
1134    let utc = newTimezone("Etc/UTC", utcTzInfo, utcTzInfo)
1135  Timezone(
1136    name: name,
1137    zonedTimeFromTimeImpl: zonedTimeFromTimeImpl,
1138    zonedTimeFromAdjTimeImpl: zonedTimeFromAdjTimeImpl
1139  )
1140
1141proc name*(zone: Timezone): string =
1142  ## The name of the timezone.
1143  ##
1144  ## If possible, the name will be the name used in the tz database.
1145  ## If the timezone doesn't exist in the tz database, or if the timezone
1146  ## name is unknown, then any string that describes the timezone
1147  ## unambiguously might be used. For example, the string "LOCAL" is used
1148  ## for the system's local timezone.
1149  ##
1150  ## See also: https://en.wikipedia.org/wiki/Tz_database
1151  zone.name
1152
1153proc zonedTimeFromTime*(zone: Timezone, time: Time): ZonedTime =
1154  ## Returns the `ZonedTime` for some point in time.
1155  zone.zonedTimeFromTimeImpl(time)
1156
1157proc zonedTimeFromAdjTime*(zone: Timezone, adjTime: Time): ZonedTime =
1158  ## Returns the `ZonedTime` for some local time.
1159  ##
1160  ## Note that the `Time` argument does not represent a point in time, it
1161  ## represent a local time! E.g if `adjTime` is `fromUnix(0)`, it should be
1162  ## interpreted as 1970-01-01T00:00:00 in the `zone` timezone, not in UTC.
1163  zone.zonedTimeFromAdjTimeImpl(adjTime)
1164
1165proc `$`*(zone: Timezone): string =
1166  ## Returns the name of the timezone.
1167  if zone != nil: result = zone.name
1168
1169proc `==`*(zone1, zone2: Timezone): bool =
1170  ## Two `Timezone`'s are considered equal if their name is equal.
1171  runnableExamples:
1172    doAssert local() == local()
1173    doAssert local() != utc()
1174  if system.`==`(zone1, zone2):
1175    return true
1176  if zone1.isNil or zone2.isNil:
1177    return false
1178  zone1.name == zone2.name
1179
1180proc inZone*(time: Time, zone: Timezone): DateTime
1181    {.tags: [], raises: [], benign.} =
1182  ## Convert `time` into a `DateTime` using `zone` as the timezone.
1183  result = initDateTime(zone.zonedTimeFromTime(time), zone)
1184
1185proc inZone*(dt: DateTime, zone: Timezone): DateTime
1186    {.tags: [], raises: [], benign.} =
1187  ## Returns a `DateTime` representing the same point in time as `dt` but
1188  ## using `zone` as the timezone.
1189  assertDateTimeInitialized dt
1190  dt.toTime.inZone(zone)
1191
1192proc toAdjTime(dt: DateTime): Time =
1193  let epochDay = toEpochDay(dt.monthday, dt.month, dt.year)
1194  var seconds = epochDay * secondsInDay
1195  seconds.inc dt.hour * secondsInHour
1196  seconds.inc dt.minute * secondsInMin
1197  seconds.inc dt.second
1198  result = initTime(seconds, dt.nanosecond)
1199
1200when defined(js):
1201  proc localZonedTimeFromTime(time: Time): ZonedTime {.benign.} =
1202    let jsDate = newDate(time.seconds * 1000)
1203    let offset = jsDate.getTimezoneOffset() * secondsInMin
1204    result.time = time
1205    result.utcOffset = offset
1206    result.isDst = false
1207
1208  proc localZonedTimeFromAdjTime(adjTime: Time): ZonedTime {.benign.} =
1209    let utcDate = newDate(adjTime.seconds * 1000)
1210    let localDate = newDate(utcDate.getUTCFullYear(), utcDate.getUTCMonth(),
1211        utcDate.getUTCDate(), utcDate.getUTCHours(), utcDate.getUTCMinutes(),
1212        utcDate.getUTCSeconds(), 0)
1213
1214    # This is as dumb as it looks - JS doesn't support years in the range
1215    # 0-99 in the constructor because they are assumed to be 19xx...
1216    # Because JS doesn't support timezone history,
1217    # it doesn't really matter in practice.
1218    if utcDate.getUTCFullYear() in 0 .. 99:
1219      localDate.setFullYear(utcDate.getUTCFullYear())
1220
1221    result.utcOffset = localDate.getTimezoneOffset() * secondsInMin
1222    result.time = adjTime + initDuration(seconds = result.utcOffset)
1223    result.isDst = false
1224
1225else:
1226  proc toAdjUnix(tm: Tm): int64 =
1227    let epochDay = toEpochDay(tm.tm_mday, (tm.tm_mon + 1).Month,
1228                              tm.tm_year.int + 1900)
1229    result = epochDay * secondsInDay
1230    result.inc tm.tm_hour * secondsInHour
1231    result.inc tm.tm_min * 60
1232    result.inc tm.tm_sec
1233
1234  proc getLocalOffsetAndDst(unix: int64): tuple[offset: int, dst: bool] =
1235    # Windows can't handle unix < 0, so we fall back to unix = 0.
1236    # FIXME: This should be improved by falling back to the WinAPI instead.
1237    when defined(windows):
1238      if unix < 0:
1239        var a = 0.CTime
1240        let tmPtr = localtime(a)
1241        if not tmPtr.isNil:
1242          let tm = tmPtr[]
1243          return ((0 - tm.toAdjUnix).int, false)
1244        return (0, false)
1245
1246    # In case of a 32-bit time_t, we fallback to the closest available
1247    # timezone information.
1248    var a = clamp(unix, low(CTime).int64, high(CTime).int64).CTime
1249    let tmPtr = localtime(a)
1250    if not tmPtr.isNil:
1251      let tm = tmPtr[]
1252      return ((a.int64 - tm.toAdjUnix).int, tm.tm_isdst > 0)
1253    return (0, false)
1254
1255  proc localZonedTimeFromTime(time: Time): ZonedTime {.benign.} =
1256    let (offset, dst) = getLocalOffsetAndDst(time.seconds)
1257    result.time = time
1258    result.utcOffset = offset
1259    result.isDst = dst
1260
1261  proc localZonedTimeFromAdjTime(adjTime: Time): ZonedTime {.benign.} =
1262    var adjUnix = adjTime.seconds
1263    let past = adjUnix - secondsInDay
1264    let (pastOffset, _) = getLocalOffsetAndDst(past)
1265
1266    let future = adjUnix + secondsInDay
1267    let (futureOffset, _) = getLocalOffsetAndDst(future)
1268
1269    var utcOffset: int
1270    if pastOffset == futureOffset:
1271      utcOffset = pastOffset.int
1272    else:
1273      if pastOffset > futureOffset:
1274        adjUnix -= secondsInHour
1275
1276      adjUnix += pastOffset
1277      utcOffset = getLocalOffsetAndDst(adjUnix).offset
1278
1279    # This extra roundtrip is needed to normalize any impossible datetimes
1280    # as a result of offset changes (normally due to dst)
1281    let utcUnix = adjTime.seconds + utcOffset
1282    let (finalOffset, dst) = getLocalOffsetAndDst(utcUnix)
1283    result.time = initTime(utcUnix, adjTime.nanosecond)
1284    result.utcOffset = finalOffset
1285    result.isDst = dst
1286
1287proc utcTzInfo(time: Time): ZonedTime =
1288  ZonedTime(utcOffset: 0, isDst: false, time: time)
1289
1290var utcInstance {.threadvar.}: Timezone
1291var localInstance {.threadvar.}: Timezone
1292
1293proc utc*(): Timezone =
1294  ## Get the `Timezone` implementation for the UTC timezone.
1295  runnableExamples:
1296    doAssert now().utc.timezone == utc()
1297    doAssert utc().name == "Etc/UTC"
1298  if utcInstance.isNil:
1299    utcInstance = newTimezone("Etc/UTC", utcTzInfo, utcTzInfo)
1300  result = utcInstance
1301
1302proc local*(): Timezone =
1303  ## Get the `Timezone` implementation for the local timezone.
1304  runnableExamples:
1305    doAssert now().timezone == local()
1306    doAssert local().name == "LOCAL"
1307  if localInstance.isNil:
1308    localInstance = newTimezone("LOCAL", localZonedTimeFromTime,
1309      localZonedTimeFromAdjTime)
1310  result = localInstance
1311
1312proc utc*(dt: DateTime): DateTime =
1313  ## Shorthand for `dt.inZone(utc())`.
1314  dt.inZone(utc())
1315
1316proc local*(dt: DateTime): DateTime =
1317  ## Shorthand for `dt.inZone(local())`.
1318  dt.inZone(local())
1319
1320proc utc*(t: Time): DateTime =
1321  ## Shorthand for `t.inZone(utc())`.
1322  t.inZone(utc())
1323
1324proc local*(t: Time): DateTime =
1325  ## Shorthand for `t.inZone(local())`.
1326  t.inZone(local())
1327
1328proc now*(): DateTime {.tags: [TimeEffect], benign.} =
1329  ## Get the current time as a  `DateTime` in the local timezone.
1330  ## Shorthand for `getTime().local`.
1331  ##
1332  ## .. warning:: Unsuitable for benchmarking, use `monotimes.getMonoTime` or
1333  ##    `cpuTime` instead, depending on the use case.
1334  getTime().local
1335
1336proc dateTime*(year: int, month: Month, monthday: MonthdayRange,
1337               hour: HourRange = 0, minute: MinuteRange = 0, second: SecondRange = 0,
1338               nanosecond: NanosecondRange = 0,
1339               zone: Timezone = local()): DateTime =
1340  ## Create a new `DateTime <#DateTime>`_ in the specified timezone.
1341  runnableExamples:
1342    assert $dateTime(2017, mMar, 30, zone = utc()) == "2017-03-30T00:00:00Z"
1343
1344  assertValidDate monthday, month, year
1345  let dt = DateTime(
1346    monthdayZero: monthday,
1347    year: year,
1348    monthZero: month.int,
1349    hour: hour,
1350    minute: minute,
1351    second: second,
1352    nanosecond: nanosecond
1353  )
1354  result = initDateTime(zone.zonedTimeFromAdjTime(dt.toAdjTime), zone)
1355
1356proc initDateTime*(monthday: MonthdayRange, month: Month, year: int,
1357                   hour: HourRange, minute: MinuteRange, second: SecondRange,
1358                   nanosecond: NanosecondRange,
1359                   zone: Timezone = local()): DateTime {.deprecated: "use `dateTime`".} =
1360  ## Create a new `DateTime <#DateTime>`_ in the specified timezone.
1361  runnableExamples("--warning:deprecated:off"):
1362    assert $initDateTime(30, mMar, 2017, 00, 00, 00, 00, utc()) == "2017-03-30T00:00:00Z"
1363  dateTime(year, month, monthday, hour, minute, second, nanosecond, zone)
1364
1365proc initDateTime*(monthday: MonthdayRange, month: Month, year: int,
1366                   hour: HourRange, minute: MinuteRange, second: SecondRange,
1367                   zone: Timezone = local()): DateTime {.deprecated: "use `dateTime`".} =
1368  ## Create a new `DateTime <#DateTime>`_ in the specified timezone.
1369  runnableExamples("--warning:deprecated:off"):
1370    assert $initDateTime(30, mMar, 2017, 00, 00, 00, utc()) == "2017-03-30T00:00:00Z"
1371  dateTime(year, month, monthday, hour, minute, second, 0, zone)
1372
1373proc `+`*(dt: DateTime, dur: Duration): DateTime =
1374  runnableExamples:
1375    let dt = dateTime(2017, mMar, 30, 00, 00, 00, 00, utc())
1376    let dur = initDuration(hours = 5)
1377    doAssert $(dt + dur) == "2017-03-30T05:00:00Z"
1378
1379  (dt.toTime + dur).inZone(dt.timezone)
1380
1381proc `-`*(dt: DateTime, dur: Duration): DateTime =
1382  runnableExamples:
1383    let dt = dateTime(2017, mMar, 30, 00, 00, 00, 00, utc())
1384    let dur = initDuration(days = 5)
1385    doAssert $(dt - dur) == "2017-03-25T00:00:00Z"
1386
1387  (dt.toTime - dur).inZone(dt.timezone)
1388
1389proc `-`*(dt1, dt2: DateTime): Duration =
1390  ## Compute the duration between `dt1` and `dt2`.
1391  runnableExamples:
1392    let dt1 = dateTime(2017, mMar, 30, 00, 00, 00, 00, utc())
1393    let dt2 = dateTime(2017, mMar, 25, 00, 00, 00, 00, utc())
1394
1395    doAssert dt1 - dt2 == initDuration(days = 5)
1396
1397  dt1.toTime - dt2.toTime
1398
1399proc `<`*(a, b: DateTime): bool =
1400  ## Returns true if `a` happened before `b`.
1401  return a.toTime < b.toTime
1402
1403proc `<=`*(a, b: DateTime): bool =
1404  ## Returns true if `a` happened before or at the same time as `b`.
1405  return a.toTime <= b.toTime
1406
1407proc `==`*(a, b: DateTime): bool =
1408  ## Returns true if `a` and `b` represent the same point in time.
1409  if not a.isInitialized: not b.isInitialized
1410  elif not b.isInitialized: false
1411  else: a.toTime == b.toTime
1412
1413proc `+=`*(a: var DateTime, b: Duration) =
1414  a = a + b
1415
1416proc `-=`*(a: var DateTime, b: Duration) =
1417  a = a - b
1418
1419proc getDateStr*(dt = now()): string {.rtl, extern: "nt$1", tags: [TimeEffect].} =
1420  ## Gets the current local date as a string of the format `YYYY-MM-DD`.
1421  runnableExamples:
1422    echo getDateStr(now() - 1.months)
1423  assertDateTimeInitialized dt
1424  result = $dt.year & '-' & intToStr(dt.monthZero, 2) &
1425    '-' & intToStr(dt.monthday, 2)
1426
1427proc getClockStr*(dt = now()): string {.rtl, extern: "nt$1", tags: [TimeEffect].} =
1428  ## Gets the current local clock time as a string of the format `HH:mm:ss`.
1429  runnableExamples:
1430    echo getClockStr(now() - 1.hours)
1431  assertDateTimeInitialized dt
1432  result = intToStr(dt.hour, 2) & ':' & intToStr(dt.minute, 2) &
1433    ':' & intToStr(dt.second, 2)
1434
1435#
1436# TimeFormat
1437#
1438
1439when defined(nimHasStyleChecks):
1440  {.push styleChecks: off.}
1441
1442type
1443  DateTimeLocale* = object
1444    MMM*: array[mJan..mDec, string]
1445    MMMM*: array[mJan..mDec, string]
1446    ddd*: array[dMon..dSun, string]
1447    dddd*: array[dMon..dSun, string]
1448
1449when defined(nimHasStyleChecks):
1450  {.pop.}
1451
1452type
1453  AmPm = enum
1454    apUnknown, apAm, apPm
1455
1456  Era = enum
1457    eraUnknown, eraAd, eraBc
1458
1459  ParsedTime = object
1460    amPm: AmPm
1461    era: Era
1462    year: Option[int]
1463    month: Option[int]
1464    monthday: Option[int]
1465    utcOffset: Option[int]
1466
1467    # '0' as default for these work fine
1468    # so no need for `Option`.
1469    hour: int
1470    minute: int
1471    second: int
1472    nanosecond: int
1473
1474  FormatTokenKind = enum
1475    tkPattern, tkLiteral
1476
1477  FormatPattern {.pure.} = enum
1478    d, dd, ddd, dddd
1479    h, hh, H, HH
1480    m, mm, M, MM, MMM, MMMM
1481    s, ss
1482    fff, ffffff, fffffffff
1483    t, tt
1484    yy, yyyy
1485    YYYY
1486    uuuu
1487    UUUU
1488    z, zz, zzz, zzzz
1489    ZZZ, ZZZZ
1490    g
1491
1492    # This is a special value used to mark literal format values.
1493    # See the doc comment for `TimeFormat.patterns`.
1494    Lit
1495
1496  TimeFormat* = object  ## Represents a format for parsing and printing
1497                        ## time types.
1498                        ##
1499                        ## To create a new `TimeFormat` use `initTimeFormat proc
1500                        ## <#initTimeFormat,string>`_.
1501    patterns: seq[byte] ## \
1502      ## Contains the patterns encoded as bytes.
1503      ## Literal values are encoded in a special way.
1504      ## They start with `Lit.byte`, then the length of the literal, then the
1505      ## raw char values of the literal. For example, the literal `foo` would
1506      ## be encoded as `@[Lit.byte, 3.byte, 'f'.byte, 'o'.byte, 'o'.byte]`.
1507    formatStr: string
1508
1509  TimeParseError* = object of ValueError ## \
1510    ## Raised when parsing input using a `TimeFormat` fails.
1511
1512  TimeFormatParseError* = object of ValueError ## \
1513    ## Raised when parsing a `TimeFormat` string fails.
1514
1515const
1516  DefaultLocale* = DateTimeLocale(
1517    MMM: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct",
1518        "Nov", "Dec"],
1519    MMMM: ["January", "February", "March", "April", "May", "June", "July",
1520        "August", "September", "October", "November", "December"],
1521    ddd: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
1522    dddd: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday",
1523        "Sunday"],
1524  )
1525
1526  FormatLiterals = {' ', '-', '/', ':', '(', ')', '[', ']', ','}
1527
1528proc `$`*(f: TimeFormat): string =
1529  ## Returns the format string that was used to construct `f`.
1530  runnableExamples:
1531    let f = initTimeFormat("yyyy-MM-dd")
1532    doAssert $f == "yyyy-MM-dd"
1533  f.formatStr
1534
1535proc raiseParseException(f: TimeFormat, input: string, msg: string) =
1536  raise newException(TimeParseError,
1537                     "Failed to parse '" & input & "' with format '" & $f &
1538                     "'. " & msg)
1539
1540proc parseInt(s: string, b: var int, start = 0, maxLen = int.high,
1541              allowSign = false): int =
1542  var sign = -1
1543  var i = start
1544  let stop = start + min(s.high - start + 1, maxLen) - 1
1545  if allowSign and i <= stop:
1546    if s[i] == '+':
1547      inc(i)
1548    elif s[i] == '-':
1549      inc(i)
1550      sign = 1
1551  if i <= stop and s[i] in {'0'..'9'}:
1552    b = 0
1553    while i <= stop and s[i] in {'0'..'9'}:
1554      let c = ord(s[i]) - ord('0')
1555      if b >= (low(int) + c) div 10:
1556        b = b * 10 - c
1557      else:
1558        return 0
1559      inc(i)
1560    if sign == -1 and b == low(int):
1561      return 0
1562    b = b * sign
1563    result = i - start
1564
1565iterator tokens(f: string): tuple[kind: FormatTokenKind, token: string] =
1566  var i = 0
1567  var currToken = ""
1568
1569  template yieldCurrToken() =
1570    if currToken.len != 0:
1571      yield (tkPattern, currToken)
1572      currToken = ""
1573
1574  while i < f.len:
1575    case f[i]
1576    of '\'':
1577      yieldCurrToken()
1578      if i.succ < f.len and f[i.succ] == '\'':
1579        yield (tkLiteral, "'")
1580        i.inc 2
1581      else:
1582        var token = ""
1583        inc(i) # Skip '
1584        while i < f.len and f[i] != '\'':
1585          token.add f[i]
1586          i.inc
1587
1588        if i > f.high:
1589          raise newException(TimeFormatParseError,
1590                             "Unclosed ' in time format string. " &
1591                             "For a literal ', use ''.")
1592        i.inc
1593        yield (tkLiteral, token)
1594    of FormatLiterals:
1595      yieldCurrToken()
1596      yield (tkLiteral, $f[i])
1597      i.inc
1598    else:
1599      # Check if the letter being added matches previous accumulated buffer.
1600      if currToken.len == 0 or currToken[0] == f[i]:
1601        currToken.add(f[i])
1602        i.inc
1603      else:
1604        yield (tkPattern, currToken)
1605        currToken = $f[i]
1606        i.inc
1607
1608  yieldCurrToken()
1609
1610proc stringToPattern(str: string): FormatPattern =
1611  case str
1612  of "d": result = d
1613  of "dd": result = dd
1614  of "ddd": result = ddd
1615  of "dddd": result = dddd
1616  of "h": result = h
1617  of "hh": result = hh
1618  of "H": result = H
1619  of "HH": result = HH
1620  of "m": result = m
1621  of "mm": result = mm
1622  of "M": result = M
1623  of "MM": result = MM
1624  of "MMM": result = MMM
1625  of "MMMM": result = MMMM
1626  of "s": result = s
1627  of "ss": result = ss
1628  of "fff": result = fff
1629  of "ffffff": result = ffffff
1630  of "fffffffff": result = fffffffff
1631  of "t": result = t
1632  of "tt": result = tt
1633  of "yy": result = yy
1634  of "yyyy": result = yyyy
1635  of "YYYY": result = YYYY
1636  of "uuuu": result = uuuu
1637  of "UUUU": result = UUUU
1638  of "z": result = z
1639  of "zz": result = zz
1640  of "zzz": result = zzz
1641  of "zzzz": result = zzzz
1642  of "ZZZ": result = ZZZ
1643  of "ZZZZ": result = ZZZZ
1644  of "g": result = g
1645  else: raise newException(TimeFormatParseError,
1646                           "'" & str & "' is not a valid pattern")
1647
1648proc initTimeFormat*(format: string): TimeFormat =
1649  ## Construct a new time format for parsing & formatting time types.
1650  ##
1651  ## See `Parsing and formatting dates`_ for documentation of the
1652  ## `format` argument.
1653  runnableExamples:
1654    let f = initTimeFormat("yyyy-MM-dd")
1655    doAssert "2000-01-01" == "2000-01-01".parse(f).format(f)
1656  result.formatStr = format
1657  result.patterns = @[]
1658  for kind, token in format.tokens:
1659    case kind
1660    of tkLiteral:
1661      case token
1662      else:
1663        result.patterns.add(FormatPattern.Lit.byte)
1664        if token.len > 255:
1665          raise newException(TimeFormatParseError,
1666                             "Format literal is to long:" & token)
1667        result.patterns.add(token.len.byte)
1668        for c in token:
1669          result.patterns.add(c.byte)
1670    of tkPattern:
1671      result.patterns.add(stringToPattern(token).byte)
1672
1673proc formatPattern(dt: DateTime, pattern: FormatPattern, result: var string,
1674    loc: DateTimeLocale) =
1675  template yearOfEra(dt: DateTime): int =
1676    if dt.year <= 0: abs(dt.year) + 1 else: dt.year
1677
1678  case pattern
1679  of d:
1680    result.add $dt.monthday
1681  of dd:
1682    result.add dt.monthday.intToStr(2)
1683  of ddd:
1684    result.add loc.ddd[dt.weekday]
1685  of dddd:
1686    result.add loc.dddd[dt.weekday]
1687  of h:
1688    result.add(
1689      if dt.hour == 0: "12"
1690      elif dt.hour > 12: $(dt.hour - 12)
1691      else: $dt.hour
1692    )
1693  of hh:
1694    result.add(
1695      if dt.hour == 0: "12"
1696      elif dt.hour > 12: (dt.hour - 12).intToStr(2)
1697      else: dt.hour.intToStr(2)
1698    )
1699  of H:
1700    result.add $dt.hour
1701  of HH:
1702    result.add dt.hour.intToStr(2)
1703  of m:
1704    result.add $dt.minute
1705  of mm:
1706    result.add dt.minute.intToStr(2)
1707  of M:
1708    result.add $ord(dt.month)
1709  of MM:
1710    result.add ord(dt.month).intToStr(2)
1711  of MMM:
1712    result.add loc.MMM[dt.month]
1713  of MMMM:
1714    result.add loc.MMMM[dt.month]
1715  of s:
1716    result.add $dt.second
1717  of ss:
1718    result.add dt.second.intToStr(2)
1719  of fff:
1720    result.add(intToStr(convert(Nanoseconds, Milliseconds, dt.nanosecond), 3))
1721  of ffffff:
1722    result.add(intToStr(convert(Nanoseconds, Microseconds, dt.nanosecond), 6))
1723  of fffffffff:
1724    result.add(intToStr(dt.nanosecond, 9))
1725  of t:
1726    result.add if dt.hour >= 12: "P" else: "A"
1727  of tt:
1728    result.add if dt.hour >= 12: "PM" else: "AM"
1729  of yy:
1730    result.add (dt.yearOfEra mod 100).intToStr(2)
1731  of yyyy:
1732    let year = dt.yearOfEra
1733    if year < 10000:
1734      result.add year.intToStr(4)
1735    else:
1736      result.add '+' & $year
1737  of YYYY:
1738    if dt.year < 1:
1739      result.add $(abs(dt.year) + 1)
1740    else:
1741      result.add $dt.year
1742  of uuuu:
1743    let year = dt.year
1744    if year < 10000 or year < 0:
1745      result.add year.intToStr(4)
1746    else:
1747      result.add '+' & $year
1748  of UUUU:
1749    result.add $dt.year
1750  of z, zz, zzz, zzzz, ZZZ, ZZZZ:
1751    if dt.timezone != nil and dt.timezone.name == "Etc/UTC":
1752      result.add 'Z'
1753    else:
1754      result.add if -dt.utcOffset >= 0: '+' else: '-'
1755      let absOffset = abs(dt.utcOffset)
1756      case pattern:
1757      of z:
1758        result.add $(absOffset div 3600)
1759      of zz:
1760        result.add (absOffset div 3600).intToStr(2)
1761      of zzz, ZZZ:
1762        let h = (absOffset div 3600).intToStr(2)
1763        let m = ((absOffset div 60) mod 60).intToStr(2)
1764        let sep = if pattern == zzz: ":" else: ""
1765        result.add h & sep & m
1766      of zzzz, ZZZZ:
1767        let absOffset = abs(dt.utcOffset)
1768        let h = (absOffset div 3600).intToStr(2)
1769        let m = ((absOffset div 60) mod 60).intToStr(2)
1770        let s = (absOffset mod 60).intToStr(2)
1771        let sep = if pattern == zzzz: ":" else: ""
1772        result.add h & sep & m & sep & s
1773      else: assert false
1774  of g:
1775    result.add if dt.year < 1: "BC" else: "AD"
1776  of Lit: assert false # Can't happen
1777
1778proc parsePattern(input: string, pattern: FormatPattern, i: var int,
1779                  parsed: var ParsedTime, loc: DateTimeLocale): bool =
1780  template takeInt(allowedWidth: Slice[int], allowSign = false): int =
1781    var sv = 0
1782    var pd = parseInt(input, sv, i, allowedWidth.b, allowSign)
1783    if pd < allowedWidth.a:
1784      return false
1785    i.inc pd
1786    sv
1787
1788  template contains[T](t: typedesc[T], i: int): bool =
1789    i in low(t)..high(t)
1790
1791  result = true
1792
1793  case pattern
1794  of d:
1795    let monthday = takeInt(1..2)
1796    parsed.monthday = some(monthday)
1797    result = monthday in MonthdayRange
1798  of dd:
1799    let monthday = takeInt(2..2)
1800    parsed.monthday = some(monthday)
1801    result = monthday in MonthdayRange
1802  of ddd:
1803    result = false
1804    for v in loc.ddd:
1805      if input.substr(i, i+v.len-1).cmpIgnoreCase(v) == 0:
1806        result = true
1807        i.inc v.len
1808        break
1809  of dddd:
1810    result = false
1811    for v in loc.dddd:
1812      if input.substr(i, i+v.len-1).cmpIgnoreCase(v) == 0:
1813        result = true
1814        i.inc v.len
1815        break
1816  of h, H:
1817    parsed.hour = takeInt(1..2)
1818    result = parsed.hour in HourRange
1819  of hh, HH:
1820    parsed.hour = takeInt(2..2)
1821    result = parsed.hour in HourRange
1822  of m:
1823    parsed.minute = takeInt(1..2)
1824    result = parsed.hour in MinuteRange
1825  of mm:
1826    parsed.minute = takeInt(2..2)
1827    result = parsed.hour in MinuteRange
1828  of M:
1829    let month = takeInt(1..2)
1830    result = month in 1..12
1831    parsed.month = some(month)
1832  of MM:
1833    let month = takeInt(2..2)
1834    result = month in 1..12
1835    parsed.month = some(month)
1836  of MMM:
1837    result = false
1838    for n, v in loc.MMM:
1839      if input.substr(i, i+v.len-1).cmpIgnoreCase(v) == 0:
1840        result = true
1841        i.inc v.len
1842        parsed.month = some(n.int)
1843        break
1844  of MMMM:
1845    result = false
1846    for n, v in loc.MMMM:
1847      if input.substr(i, i+v.len-1).cmpIgnoreCase(v) == 0:
1848        result = true
1849        i.inc v.len
1850        parsed.month = some(n.int)
1851        break
1852  of s:
1853    parsed.second = takeInt(1..2)
1854  of ss:
1855    parsed.second = takeInt(2..2)
1856  of fff, ffffff, fffffffff:
1857    let len = ($pattern).len
1858    let v = takeInt(len..len)
1859    parsed.nanosecond = v * 10^(9 - len)
1860    result = parsed.nanosecond in NanosecondRange
1861  of t:
1862    case input[i]:
1863    of 'P':
1864      parsed.amPm = apPm
1865    of 'A':
1866      parsed.amPm = apAm
1867    else:
1868      result = false
1869    i.inc 1
1870  of tt:
1871    if input.substr(i, i+1).cmpIgnoreCase("AM") == 0:
1872      parsed.amPm = apAm
1873      i.inc 2
1874    elif input.substr(i, i+1).cmpIgnoreCase("PM") == 0:
1875      parsed.amPm = apPm
1876      i.inc 2
1877    else:
1878      result = false
1879  of yy:
1880    # Assumes current century
1881    var year = takeInt(2..2)
1882    var thisCen = now().year div 100
1883    parsed.year = some(thisCen*100 + year)
1884    result = year > 0
1885  of yyyy:
1886    let year =
1887      if input[i] in {'+', '-'}:
1888        takeInt(4..high(int), allowSign = true)
1889      else:
1890        takeInt(4..4)
1891    result = year > 0
1892    parsed.year = some(year)
1893  of YYYY:
1894    let year = takeInt(1..high(int))
1895    parsed.year = some(year)
1896    result = year > 0
1897  of uuuu:
1898    let year =
1899      if input[i] in {'+', '-'}:
1900        takeInt(4..high(int), allowSign = true)
1901      else:
1902        takeInt(4..4)
1903    parsed.year = some(year)
1904  of UUUU:
1905    parsed.year = some(takeInt(1..high(int), allowSign = true))
1906  of z, zz, zzz, zzzz, ZZZ, ZZZZ:
1907    case input[i]
1908    of '+', '-':
1909      let sign = if input[i] == '-': 1 else: -1
1910      i.inc
1911      var offset = 0
1912      case pattern
1913      of z:
1914        offset = takeInt(1..2) * 3600
1915      of zz:
1916        offset = takeInt(2..2) * 3600
1917      of zzz, ZZZ:
1918        offset.inc takeInt(2..2) * 3600
1919        if pattern == zzz:
1920          if input[i] != ':':
1921            return false
1922          i.inc
1923        offset.inc takeInt(2..2) * 60
1924      of zzzz, ZZZZ:
1925        offset.inc takeInt(2..2) * 3600
1926        if pattern == zzzz:
1927          if input[i] != ':':
1928            return false
1929          i.inc
1930        offset.inc takeInt(2..2) * 60
1931        if pattern == zzzz:
1932          if input[i] != ':':
1933            return false
1934          i.inc
1935        offset.inc takeInt(2..2)
1936      else: assert false
1937      parsed.utcOffset = some(offset * sign)
1938    of 'Z':
1939      parsed.utcOffset = some(0)
1940      i.inc
1941    else:
1942      result = false
1943  of g:
1944    if input.substr(i, i+1).cmpIgnoreCase("BC") == 0:
1945      parsed.era = eraBc
1946      i.inc 2
1947    elif input.substr(i, i+1).cmpIgnoreCase("AD") == 0:
1948      parsed.era = eraAd
1949      i.inc 2
1950    else:
1951      result = false
1952  of Lit: doAssert false, "Can't happen"
1953
1954proc toDateTime(p: ParsedTime, zone: Timezone, f: TimeFormat,
1955                input: string): DateTime =
1956  var year = p.year.get(0)
1957  var month = p.month.get(1).Month
1958  var monthday = p.monthday.get(1)
1959  year =
1960    case p.era
1961    of eraUnknown:
1962      year
1963    of eraBc:
1964      if year < 1:
1965        raiseParseException(f, input,
1966          "Expected year to be positive " &
1967          "(use 'UUUU' or 'uuuu' for negative years).")
1968      -year + 1
1969    of eraAd:
1970      if year < 1:
1971        raiseParseException(f, input,
1972          "Expected year to be positive " &
1973          "(use 'UUUU' or 'uuuu' for negative years).")
1974      year
1975
1976  let hour =
1977    case p.amPm
1978    of apUnknown:
1979      p.hour
1980    of apAm:
1981      if p.hour notin 1..12:
1982        raiseParseException(f, input,
1983          "AM/PM time must be in the interval 1..12")
1984      if p.hour == 12: 0 else: p.hour
1985    of apPm:
1986      if p.hour notin 1..12:
1987        raiseParseException(f, input,
1988          "AM/PM time must be in the interval 1..12")
1989      if p.hour == 12: p.hour else: p.hour + 12
1990  let minute = p.minute
1991  let second = p.second
1992  let nanosecond = p.nanosecond
1993
1994  if monthday > getDaysInMonth(month, year):
1995    raiseParseException(f, input,
1996      $year & "-" & ord(month).intToStr(2) &
1997      "-" & $monthday & " is not a valid date")
1998
1999  if p.utcOffset.isNone:
2000    # No timezone parsed - assume timezone is `zone`
2001    result = dateTime(year, month, monthday, hour, minute, second, nanosecond, zone)
2002  else:
2003    # Otherwise convert to `zone`
2004    result = (dateTime(year, month, monthday, hour, minute, second, nanosecond, utc()).toTime +
2005      initDuration(seconds = p.utcOffset.get())).inZone(zone)
2006
2007proc format*(dt: DateTime, f: TimeFormat,
2008    loc: DateTimeLocale = DefaultLocale): string {.raises: [].} =
2009  ## Format `dt` using the format specified by `f`.
2010  runnableExamples:
2011    let f = initTimeFormat("yyyy-MM-dd")
2012    let dt = dateTime(2000, mJan, 01, 00, 00, 00, 00, utc())
2013    doAssert "2000-01-01" == dt.format(f)
2014  assertDateTimeInitialized dt
2015  result = ""
2016  var idx = 0
2017  while idx <= f.patterns.high:
2018    case f.patterns[idx].FormatPattern
2019    of Lit:
2020      idx.inc
2021      let len = f.patterns[idx]
2022      for i in 1'u8..len:
2023        idx.inc
2024        result.add f.patterns[idx].char
2025      idx.inc
2026    else:
2027      formatPattern(dt, f.patterns[idx].FormatPattern, result = result, loc = loc)
2028      idx.inc
2029
2030proc format*(dt: DateTime, f: string, loc: DateTimeLocale = DefaultLocale): string
2031    {.raises: [TimeFormatParseError].} =
2032  ## Shorthand for constructing a `TimeFormat` and using it to format `dt`.
2033  ##
2034  ## See `Parsing and formatting dates`_ for documentation of the
2035  ## `format` argument.
2036  runnableExamples:
2037    let dt = dateTime(2000, mJan, 01, 00, 00, 00, 00, utc())
2038    doAssert "2000-01-01" == format(dt, "yyyy-MM-dd")
2039  let dtFormat = initTimeFormat(f)
2040  result = dt.format(dtFormat, loc)
2041
2042proc format*(dt: DateTime, f: static[string]): string {.raises: [].} =
2043  ## Overload that validates `format` at compile time.
2044  const f2 = initTimeFormat(f)
2045  result = dt.format(f2)
2046
2047proc formatValue*(result: var string; value: DateTime, specifier: string) =
2048  ## adapter for strformat. Not intended to be called directly.
2049  result.add format(value,
2050    if specifier.len == 0: "yyyy-MM-dd'T'HH:mm:sszzz" else: specifier)
2051
2052proc format*(time: Time, f: string, zone: Timezone = local()): string
2053    {.raises: [TimeFormatParseError].} =
2054  ## Shorthand for constructing a `TimeFormat` and using it to format
2055  ## `time`. Will use the timezone specified by `zone`.
2056  ##
2057  ## See `Parsing and formatting dates`_ for documentation of the
2058  ## `f` argument.
2059  runnableExamples:
2060    var dt = dateTime(1970, mJan, 01, 00, 00, 00, 00, utc())
2061    var tm = dt.toTime()
2062    doAssert format(tm, "yyyy-MM-dd'T'HH:mm:ss", utc()) == "1970-01-01T00:00:00"
2063  time.inZone(zone).format(f)
2064
2065proc format*(time: Time, f: static[string], zone: Timezone = local()): string
2066    {.raises: [].} =
2067  ## Overload that validates `f` at compile time.
2068  const f2 = initTimeFormat(f)
2069  result = time.inZone(zone).format(f2)
2070
2071template formatValue*(result: var string; value: Time, specifier: string) =
2072  ## adapter for `strformat`. Not intended to be called directly.
2073  result.add format(value, specifier)
2074
2075proc parse*(input: string, f: TimeFormat, zone: Timezone = local(),
2076    loc: DateTimeLocale = DefaultLocale): DateTime
2077    {.raises: [TimeParseError, Defect].} =
2078  ## Parses `input` as a `DateTime` using the format specified by `f`.
2079  ## If no UTC offset was parsed, then `input` is assumed to be specified in
2080  ## the `zone` timezone. If a UTC offset was parsed, the result will be
2081  ## converted to the `zone` timezone.
2082  ##
2083  ## Month and day names from the passed in `loc` are used.
2084  runnableExamples:
2085    let f = initTimeFormat("yyyy-MM-dd")
2086    let dt = dateTime(2000, mJan, 01, 00, 00, 00, 00, utc())
2087    doAssert dt == "2000-01-01".parse(f, utc())
2088  var inpIdx = 0 # Input index
2089  var patIdx = 0 # Pattern index
2090  var parsed: ParsedTime
2091  while inpIdx <= input.high and patIdx <= f.patterns.high:
2092    let pattern = f.patterns[patIdx].FormatPattern
2093    case pattern
2094    of Lit:
2095      patIdx.inc
2096      let len = f.patterns[patIdx]
2097      patIdx.inc
2098      for _ in 1'u8..len:
2099        if input[inpIdx] != f.patterns[patIdx].char:
2100          raiseParseException(f, input,
2101                              "Unexpected character: " & input[inpIdx])
2102        inpIdx.inc
2103        patIdx.inc
2104    else:
2105      if not parsePattern(input, pattern, inpIdx, parsed, loc):
2106        raiseParseException(f, input, "Failed on pattern '" & $pattern & "'")
2107      patIdx.inc
2108
2109  if inpIdx <= input.high:
2110    raiseParseException(f, input,
2111                        "Parsing ended but there was still input remaining")
2112
2113  if patIdx <= f.patterns.high:
2114    raiseParseException(f, input,
2115                            "Parsing ended but there was still patterns remaining")
2116
2117  result = toDateTime(parsed, zone, f, input)
2118
2119proc parse*(input, f: string, tz: Timezone = local(),
2120    loc: DateTimeLocale = DefaultLocale): DateTime
2121    {.raises: [TimeParseError, TimeFormatParseError, Defect].} =
2122  ## Shorthand for constructing a `TimeFormat` and using it to parse
2123  ## `input` as a `DateTime`.
2124  ##
2125  ## See `Parsing and formatting dates`_ for documentation of the
2126  ## `f` argument.
2127  runnableExamples:
2128    let dt = dateTime(2000, mJan, 01, 00, 00, 00, 00, utc())
2129    doAssert dt == parse("2000-01-01", "yyyy-MM-dd", utc())
2130  let dtFormat = initTimeFormat(f)
2131  result = input.parse(dtFormat, tz, loc = loc)
2132
2133proc parse*(input: string, f: static[string], zone: Timezone = local(),
2134    loc: DateTimeLocale = DefaultLocale):
2135  DateTime {.raises: [TimeParseError, Defect].} =
2136  ## Overload that validates `f` at compile time.
2137  const f2 = initTimeFormat(f)
2138  result = input.parse(f2, zone, loc = loc)
2139
2140proc parseTime*(input, f: string, zone: Timezone): Time
2141    {.raises: [TimeParseError, TimeFormatParseError, Defect].} =
2142  ## Shorthand for constructing a `TimeFormat` and using it to parse
2143  ## `input` as a `DateTime`, then converting it a `Time`.
2144  ##
2145  ## See `Parsing and formatting dates`_ for documentation of the
2146  ## `format` argument.
2147  runnableExamples:
2148    let tStr = "1970-01-01T00:00:00+00:00"
2149    doAssert parseTime(tStr, "yyyy-MM-dd'T'HH:mm:sszzz", utc()) == fromUnix(0)
2150  parse(input, f, zone).toTime()
2151
2152proc parseTime*(input: string, f: static[string], zone: Timezone): Time
2153    {.raises: [TimeParseError, Defect].} =
2154  ## Overload that validates `format` at compile time.
2155  const f2 = initTimeFormat(f)
2156  result = input.parse(f2, zone).toTime()
2157
2158proc `$`*(dt: DateTime): string {.tags: [], raises: [], benign.} =
2159  ## Converts a `DateTime` object to a string representation.
2160  ## It uses the format `yyyy-MM-dd'T'HH:mm:sszzz`.
2161  runnableExamples:
2162    let dt = dateTime(2000, mJan, 01, 12, 00, 00, 00, utc())
2163    doAssert $dt == "2000-01-01T12:00:00Z"
2164    doAssert $default(DateTime) == "Uninitialized DateTime"
2165  if not dt.isInitialized:
2166    result = "Uninitialized DateTime"
2167  else:
2168    result = format(dt, "yyyy-MM-dd'T'HH:mm:sszzz")
2169
2170proc `$`*(time: Time): string {.tags: [], raises: [], benign.} =
2171  ## Converts a `Time` value to a string representation. It will use the local
2172  ## time zone and use the format `yyyy-MM-dd'T'HH:mm:sszzz`.
2173  runnableExamples:
2174    let dt = dateTime(1970, mJan, 01, 00, 00, 00, 00, local())
2175    let tm = dt.toTime()
2176    doAssert $tm == "1970-01-01T00:00:00" & format(dt, "zzz")
2177  $time.local
2178
2179#
2180# TimeInterval
2181#
2182
2183proc initTimeInterval*(nanoseconds, microseconds, milliseconds,
2184                       seconds, minutes, hours,
2185                       days, weeks, months, years: int = 0): TimeInterval =
2186  ## Creates a new `TimeInterval <#TimeInterval>`_.
2187  ##
2188  ## This proc doesn't perform any normalization! For example,
2189  ## `initTimeInterval(hours = 24)` and `initTimeInterval(days = 1)` are
2190  ## not equal.
2191  ##
2192  ## You can also use the convenience procedures called `milliseconds`,
2193  ## `seconds`, `minutes`, `hours`, `days`, `months`, and `years`.
2194  runnableExamples:
2195    let day = initTimeInterval(hours = 24)
2196    let dt = dateTime(2000, mJan, 01, 12, 00, 00, 00, utc())
2197    doAssert $(dt + day) == "2000-01-02T12:00:00Z"
2198    doAssert initTimeInterval(hours = 24) != initTimeInterval(days = 1)
2199  result.nanoseconds = nanoseconds
2200  result.microseconds = microseconds
2201  result.milliseconds = milliseconds
2202  result.seconds = seconds
2203  result.minutes = minutes
2204  result.hours = hours
2205  result.days = days
2206  result.weeks = weeks
2207  result.months = months
2208  result.years = years
2209
2210proc `+`*(ti1, ti2: TimeInterval): TimeInterval =
2211  ## Adds two `TimeInterval` objects together.
2212  result.nanoseconds = ti1.nanoseconds + ti2.nanoseconds
2213  result.microseconds = ti1.microseconds + ti2.microseconds
2214  result.milliseconds = ti1.milliseconds + ti2.milliseconds
2215  result.seconds = ti1.seconds + ti2.seconds
2216  result.minutes = ti1.minutes + ti2.minutes
2217  result.hours = ti1.hours + ti2.hours
2218  result.days = ti1.days + ti2.days
2219  result.weeks = ti1.weeks + ti2.weeks
2220  result.months = ti1.months + ti2.months
2221  result.years = ti1.years + ti2.years
2222
2223proc `-`*(ti: TimeInterval): TimeInterval =
2224  ## Reverses a time interval
2225  runnableExamples:
2226    let day = -initTimeInterval(hours = 24)
2227    doAssert day.hours == -24
2228
2229  result = TimeInterval(
2230    nanoseconds: -ti.nanoseconds,
2231    microseconds: -ti.microseconds,
2232    milliseconds: -ti.milliseconds,
2233    seconds: -ti.seconds,
2234    minutes: -ti.minutes,
2235    hours: -ti.hours,
2236    days: -ti.days,
2237    weeks: -ti.weeks,
2238    months: -ti.months,
2239    years: -ti.years
2240  )
2241
2242proc `-`*(ti1, ti2: TimeInterval): TimeInterval =
2243  ## Subtracts TimeInterval `ti1` from `ti2`.
2244  ##
2245  ## Time components are subtracted one-by-one, see output:
2246  runnableExamples:
2247    let ti1 = initTimeInterval(hours = 24)
2248    let ti2 = initTimeInterval(hours = 4)
2249    doAssert (ti1 - ti2) == initTimeInterval(hours = 20)
2250
2251  result = ti1 + (-ti2)
2252
2253proc `+=`*(a: var TimeInterval, b: TimeInterval) =
2254  a = a + b
2255
2256proc `-=`*(a: var TimeInterval, b: TimeInterval) =
2257  a = a - b
2258
2259proc isStaticInterval(interval: TimeInterval): bool =
2260  interval.years == 0 and interval.months == 0 and
2261    interval.days == 0 and interval.weeks == 0
2262
2263proc evaluateStaticInterval(interval: TimeInterval): Duration =
2264  assert interval.isStaticInterval
2265  initDuration(nanoseconds = interval.nanoseconds,
2266    microseconds = interval.microseconds,
2267    milliseconds = interval.milliseconds,
2268    seconds = interval.seconds,
2269    minutes = interval.minutes,
2270    hours = interval.hours)
2271
2272proc between*(startDt, endDt: DateTime): TimeInterval =
2273  ## Gives the difference between `startDt` and `endDt` as a
2274  ## `TimeInterval`. The following guarantees about the result is given:
2275  ##
2276  ## - All fields will have the same sign.
2277  ## - If `startDt.timezone == endDt.timezone`, it is guaranteed that
2278  ##   `startDt + between(startDt, endDt) == endDt`.
2279  ## - If `startDt.timezone != endDt.timezone`, then the result will be
2280  ##   equivalent to `between(startDt.utc, endDt.utc)`.
2281  runnableExamples:
2282    var a = dateTime(2015, mMar, 25, 12, 0, 0, 00, utc())
2283    var b = dateTime(2017, mApr, 1, 15, 0, 15, 00, utc())
2284    var ti = initTimeInterval(years = 2, weeks = 1, hours = 3, seconds = 15)
2285    doAssert between(a, b) == ti
2286    doAssert between(a, b) == -between(b, a)
2287
2288  if startDt.timezone != endDt.timezone:
2289    return between(startDt.utc, endDt.utc)
2290  elif endDt < startDt:
2291    return -between(endDt, startDt)
2292
2293  type Date = tuple[year, month, monthday: int]
2294  var startDate: Date = (startDt.year, startDt.month.ord, startDt.monthday)
2295  var endDate: Date = (endDt.year, endDt.month.ord, endDt.monthday)
2296
2297  # Subtract one day from endDate if time of day is earlier than startDay
2298  # The subtracted day will be counted by fixed units (hour and lower)
2299  # at the end of this proc
2300  if (endDt.hour, endDt.minute, endDt.second, endDt.nanosecond) <
2301      (startDt.hour, startDt.minute, startDt.second, startDt.nanosecond):
2302    if endDate.month == 1 and endDate.monthday == 1:
2303      endDate.year.dec
2304      endDate.monthday = 31
2305      endDate.month = 12
2306    elif endDate.monthday == 1:
2307      endDate.month.dec
2308      endDate.monthday = getDaysInMonth(endDate.month.Month, endDate.year)
2309    else:
2310      endDate.monthday.dec
2311
2312  # Years
2313  result.years = endDate.year - startDate.year - 1
2314  if (startDate.month, startDate.monthday) <= (endDate.month, endDate.monthday):
2315    result.years.inc
2316  startDate.year.inc result.years
2317
2318  # Months
2319  if startDate.year < endDate.year:
2320    result.months.inc 12 - startDate.month # Move to dec
2321    if endDate.month != 1 or (startDate.monthday <= endDate.monthday):
2322      result.months.inc
2323      startDate.year = endDate.year
2324      startDate.month = 1
2325    else:
2326      startDate.month = 12
2327  if startDate.year == endDate.year:
2328    if (startDate.monthday <= endDate.monthday):
2329      result.months.inc endDate.month - startDate.month
2330      startDate.month = endDate.month
2331    elif endDate.month != 1:
2332      let month = endDate.month - 1
2333      let daysInMonth = getDaysInMonth(month.Month, startDate.year)
2334      if daysInMonth < startDate.monthday:
2335        if startDate.monthday - daysInMonth < endDate.monthday:
2336          result.months.inc endDate.month - startDate.month - 1
2337          startDate.month = endDate.month
2338          startDate.monthday = startDate.monthday - daysInMonth
2339        else:
2340          result.months.inc endDate.month - startDate.month - 2
2341          startDate.month = endDate.month - 2
2342      else:
2343        result.months.inc endDate.month - startDate.month - 1
2344        startDate.month = endDate.month - 1
2345
2346  # Days
2347  # This means that start = dec and end = jan
2348  if startDate.year < endDate.year:
2349    result.days.inc 31 - startDate.monthday + endDate.monthday
2350    startDate = endDate
2351  else:
2352    while startDate.month < endDate.month:
2353      let daysInMonth = getDaysInMonth(startDate.month.Month, startDate.year)
2354      result.days.inc daysInMonth - startDate.monthday + 1
2355      startDate.month.inc
2356      startDate.monthday = 1
2357    result.days.inc endDate.monthday - startDate.monthday
2358    result.weeks = result.days div 7
2359    result.days = result.days mod 7
2360    startDate = endDate
2361
2362  # Handle hours, minutes, seconds, milliseconds, microseconds and nanoseconds
2363  let newStartDt = dateTime(startDate.year, startDate.month.Month,
2364    startDate.monthday, startDt.hour, startDt.minute, startDt.second,
2365    startDt.nanosecond, startDt.timezone)
2366  let dur = endDt - newStartDt
2367  let parts = toParts(dur)
2368  # There can still be a full day in `parts` since `Duration` and `TimeInterval`
2369  # models days differently.
2370  result.hours = parts[Hours].int + parts[Days].int * 24
2371  result.minutes = parts[Minutes].int
2372  result.seconds = parts[Seconds].int
2373  result.milliseconds = parts[Milliseconds].int
2374  result.microseconds = parts[Microseconds].int
2375  result.nanoseconds = parts[Nanoseconds].int
2376
2377proc toParts*(ti: TimeInterval): TimeIntervalParts =
2378  ## Converts a `TimeInterval` into an array consisting of its time units,
2379  ## starting with nanoseconds and ending with years.
2380  ##
2381  ## This procedure is useful for converting `TimeInterval` values to strings.
2382  ## E.g. then you need to implement custom interval printing
2383  runnableExamples:
2384    var tp = toParts(initTimeInterval(years = 1, nanoseconds = 123))
2385    doAssert tp[Years] == 1
2386    doAssert tp[Nanoseconds] == 123
2387
2388  var index = 0
2389  for name, value in fieldPairs(ti):
2390    result[index.TimeUnit()] = value
2391    index += 1
2392
2393proc `$`*(ti: TimeInterval): string =
2394  ## Get string representation of `TimeInterval`.
2395  runnableExamples:
2396    doAssert $initTimeInterval(years = 1, nanoseconds = 123) ==
2397      "1 year and 123 nanoseconds"
2398    doAssert $initTimeInterval() == "0 nanoseconds"
2399
2400  var parts: seq[string] = @[]
2401  var tiParts = toParts(ti)
2402  for unit in countdown(Years, Nanoseconds):
2403    if tiParts[unit] != 0:
2404      parts.add(stringifyUnit(tiParts[unit], unit))
2405
2406  result = humanizeParts(parts)
2407
2408proc nanoseconds*(nanos: int): TimeInterval {.inline.} =
2409  ## TimeInterval of `nanos` nanoseconds.
2410  initTimeInterval(nanoseconds = nanos)
2411
2412proc microseconds*(micros: int): TimeInterval {.inline.} =
2413  ## TimeInterval of `micros` microseconds.
2414  initTimeInterval(microseconds = micros)
2415
2416proc milliseconds*(ms: int): TimeInterval {.inline.} =
2417  ## TimeInterval of `ms` milliseconds.
2418  initTimeInterval(milliseconds = ms)
2419
2420proc seconds*(s: int): TimeInterval {.inline.} =
2421  ## TimeInterval of `s` seconds.
2422  ##
2423  ## `echo getTime() + 5.seconds`
2424  initTimeInterval(seconds = s)
2425
2426proc minutes*(m: int): TimeInterval {.inline.} =
2427  ## TimeInterval of `m` minutes.
2428  ##
2429  ## `echo getTime() + 5.minutes`
2430  initTimeInterval(minutes = m)
2431
2432proc hours*(h: int): TimeInterval {.inline.} =
2433  ## TimeInterval of `h` hours.
2434  ##
2435  ## `echo getTime() + 2.hours`
2436  initTimeInterval(hours = h)
2437
2438proc days*(d: int): TimeInterval {.inline.} =
2439  ## TimeInterval of `d` days.
2440  ##
2441  ## `echo getTime() + 2.days`
2442  initTimeInterval(days = d)
2443
2444proc weeks*(w: int): TimeInterval {.inline.} =
2445  ## TimeInterval of `w` weeks.
2446  ##
2447  ## `echo getTime() + 2.weeks`
2448  initTimeInterval(weeks = w)
2449
2450proc months*(m: int): TimeInterval {.inline.} =
2451  ## TimeInterval of `m` months.
2452  ##
2453  ## `echo getTime() + 2.months`
2454  initTimeInterval(months = m)
2455
2456proc years*(y: int): TimeInterval {.inline.} =
2457  ## TimeInterval of `y` years.
2458  ##
2459  ## `echo getTime() + 2.years`
2460  initTimeInterval(years = y)
2461
2462proc evaluateInterval(dt: DateTime, interval: TimeInterval):
2463    tuple[adjDur, absDur: Duration] =
2464  ## Evaluates how many nanoseconds the interval is worth
2465  ## in the context of `dt`.
2466  ## The result in split into an adjusted diff and an absolute diff.
2467  var months = interval.years * 12 + interval.months
2468  var curYear = dt.year
2469  var curMonth = dt.month
2470  result = default(tuple[adjDur, absDur: Duration])
2471  # Subtracting
2472  if months < 0:
2473    for mth in countdown(-1 * months, 1):
2474      if curMonth == mJan:
2475        curMonth = mDec
2476        curYear.dec
2477      else:
2478        curMonth.dec()
2479      let days = getDaysInMonth(curMonth, curYear)
2480      result.adjDur = result.adjDur - initDuration(days = days)
2481  # Adding
2482  else:
2483    for mth in 1 .. months:
2484      let days = getDaysInMonth(curMonth, curYear)
2485      result.adjDur = result.adjDur + initDuration(days = days)
2486      if curMonth == mDec:
2487        curMonth = mJan
2488        curYear.inc
2489      else:
2490        curMonth.inc()
2491
2492  result.adjDur = result.adjDur + initDuration(
2493    days = interval.days,
2494    weeks = interval.weeks)
2495  result.absDur = initDuration(
2496    nanoseconds = interval.nanoseconds,
2497    microseconds = interval.microseconds,
2498    milliseconds = interval.milliseconds,
2499    seconds = interval.seconds,
2500    minutes = interval.minutes,
2501    hours = interval.hours)
2502
2503proc `+`*(dt: DateTime, interval: TimeInterval): DateTime =
2504  ## Adds `interval` to `dt`. Components from `interval` are added
2505  ## in the order of their size, i.e. first the `years` component, then the
2506  ## `months` component and so on. The returned `DateTime` will have the
2507  ## same timezone as the input.
2508  ##
2509  ## Note that when adding months, monthday overflow is allowed. This means that
2510  ## if the resulting month doesn't have enough days it, the month will be
2511  ## incremented and the monthday will be set to the number of days overflowed.
2512  ## So adding one month to `31 October` will result in `31 November`, which
2513  ## will overflow and result in `1 December`.
2514  runnableExamples:
2515    let dt = dateTime(2017, mMar, 30, 00, 00, 00, 00, utc())
2516    doAssert $(dt + 1.months) == "2017-04-30T00:00:00Z"
2517    # This is correct and happens due to monthday overflow.
2518    doAssert $(dt - 1.months) == "2017-03-02T00:00:00Z"
2519  let (adjDur, absDur) = evaluateInterval(dt, interval)
2520
2521  if adjDur != DurationZero:
2522    var zt = dt.timezone.zonedTimeFromAdjTime(dt.toAdjTime + adjDur)
2523    if absDur != DurationZero:
2524      zt = dt.timezone.zonedTimeFromTime(zt.time + absDur)
2525      result = initDateTime(zt, dt.timezone)
2526    else:
2527      result = initDateTime(zt, dt.timezone)
2528  else:
2529    var zt = dt.timezone.zonedTimeFromTime(dt.toTime + absDur)
2530    result = initDateTime(zt, dt.timezone)
2531
2532proc `-`*(dt: DateTime, interval: TimeInterval): DateTime =
2533  ## Subtract `interval` from `dt`. Components from `interval` are
2534  ## subtracted in the order of their size, i.e. first the `years` component,
2535  ## then the `months` component and so on. The returned `DateTime` will
2536  ## have the same timezone as the input.
2537  runnableExamples:
2538    let dt = dateTime(2017, mMar, 30, 00, 00, 00, 00, utc())
2539    doAssert $(dt - 5.days) == "2017-03-25T00:00:00Z"
2540
2541  dt + (-interval)
2542
2543proc `+`*(time: Time, interval: TimeInterval): Time =
2544  ## Adds `interval` to `time`.
2545  ## If `interval` contains any years, months, weeks or days the operation
2546  ## is performed in the local timezone.
2547  runnableExamples:
2548    let tm = fromUnix(0)
2549    doAssert tm + 5.seconds == fromUnix(5)
2550
2551  if interval.isStaticInterval:
2552    time + evaluateStaticInterval(interval)
2553  else:
2554    toTime(time.local + interval)
2555
2556proc `-`*(time: Time, interval: TimeInterval): Time =
2557  ## Subtracts `interval` from Time `time`.
2558  ## If `interval` contains any years, months, weeks or days the operation
2559  ## is performed in the local timezone.
2560  runnableExamples:
2561    let tm = fromUnix(5)
2562    doAssert tm - 5.seconds == fromUnix(0)
2563
2564  if interval.isStaticInterval:
2565    time - evaluateStaticInterval(interval)
2566  else:
2567    toTime(time.local - interval)
2568
2569proc `+=`*(a: var DateTime, b: TimeInterval) =
2570  a = a + b
2571
2572proc `-=`*(a: var DateTime, b: TimeInterval) =
2573  a = a - b
2574
2575proc `+=`*(t: var Time, b: TimeInterval) =
2576  t = t + b
2577
2578proc `-=`*(t: var Time, b: TimeInterval) =
2579  t = t - b
2580
2581#
2582# Other
2583#
2584
2585proc epochTime*(): float {.tags: [TimeEffect].} =
2586  ## Gets time after the UNIX epoch (1970) in seconds. It is a float
2587  ## because sub-second resolution is likely to be supported (depending
2588  ## on the hardware/OS).
2589  ##
2590  ## `getTime` should generally be preferred over this proc.
2591  ##
2592  ## .. warning:: Unsuitable for benchmarking (but still better than `now`),
2593  ##    use `monotimes.getMonoTime` or `cpuTime` instead, depending on the use case.
2594  when defined(macosx):
2595    var a {.noinit.}: Timeval
2596    gettimeofday(a)
2597    result = toBiggestFloat(a.tv_sec.int64) + toBiggestFloat(
2598        a.tv_usec)*0.00_0001
2599  elif defined(posix):
2600    var ts {.noinit.}: Timespec
2601    discard clock_gettime(CLOCK_REALTIME, ts)
2602    result = toBiggestFloat(ts.tv_sec.int64) +
2603      toBiggestFloat(ts.tv_nsec.int64) / 1_000_000_000
2604  elif defined(windows):
2605    var f {.noinit.}: winlean.FILETIME
2606    getSystemTimeAsFileTime(f)
2607    var i64 = rdFileTime(f) - epochDiff
2608    var secs = i64 div rateDiff
2609    var subsecs = i64 mod rateDiff
2610    result = toFloat(int(secs)) + toFloat(int(subsecs)) * 0.0000001
2611  elif defined(js):
2612    result = newDate().getTime() / 1000
2613  else:
2614    {.error: "unknown OS".}
2615
2616when not defined(js):
2617  type
2618    Clock {.importc: "clock_t".} = distinct int
2619
2620  proc getClock(): Clock
2621      {.importc: "clock", header: "<time.h>", tags: [TimeEffect], used, sideEffect.}
2622
2623  var
2624    clocksPerSec {.importc: "CLOCKS_PER_SEC", nodecl, used.}: int
2625
2626  proc cpuTime*(): float {.tags: [TimeEffect].} =
2627    ## Gets time spent that the CPU spent to run the current process in
2628    ## seconds. This may be more useful for benchmarking than `epochTime`.
2629    ## However, it may measure the real time instead (depending on the OS).
2630    ## The value of the result has no meaning.
2631    ## To generate useful timing values, take the difference between
2632    ## the results of two `cpuTime` calls:
2633    runnableExamples:
2634      var t0 = cpuTime()
2635      # some useless work here (calculate fibonacci)
2636      var fib = @[0, 1, 1]
2637      for i in 1..10:
2638        fib.add(fib[^1] + fib[^2])
2639      echo "CPU time [s] ", cpuTime() - t0
2640      echo "Fib is [s] ", fib
2641    ## When the flag `--benchmarkVM` is passed to the compiler, this proc is
2642    ## also available at compile time
2643    when defined(posix) and not defined(osx) and declared(CLOCK_THREAD_CPUTIME_ID):
2644      # 'clocksPerSec' is a compile-time constant, possibly a
2645      # rather awful one, so use clock_gettime instead
2646      var ts: Timespec
2647      discard clock_gettime(CLOCK_THREAD_CPUTIME_ID, ts)
2648      result = toFloat(ts.tv_sec.int) +
2649        toFloat(ts.tv_nsec.int) / 1_000_000_000
2650    else:
2651      result = toFloat(int(getClock())) / toFloat(clocksPerSec)
2652
2653
2654#
2655# Deprecations
2656#
2657
2658proc `nanosecond=`*(dt: var DateTime, value: NanosecondRange) {.deprecated: "Deprecated since v1.3.1".} =
2659  dt.nanosecond = value
2660
2661proc `second=`*(dt: var DateTime, value: SecondRange) {.deprecated: "Deprecated since v1.3.1".} =
2662  dt.second = value
2663
2664proc `minute=`*(dt: var DateTime, value: MinuteRange) {.deprecated: "Deprecated since v1.3.1".} =
2665  dt.minute = value
2666
2667proc `hour=`*(dt: var DateTime, value: HourRange) {.deprecated: "Deprecated since v1.3.1".} =
2668  dt.hour = value
2669
2670proc `monthdayZero=`*(dt: var DateTime, value: int) {.deprecated: "Deprecated since v1.3.1".} =
2671  dt.monthdayZero = value
2672
2673proc `monthZero=`*(dt: var DateTime, value: int) {.deprecated: "Deprecated since v1.3.1".} =
2674  dt.monthZero = value
2675
2676proc `year=`*(dt: var DateTime, value: int) {.deprecated: "Deprecated since v1.3.1".} =
2677  dt.year = value
2678
2679proc `weekday=`*(dt: var DateTime, value: WeekDay) {.deprecated: "Deprecated since v1.3.1".} =
2680  dt.weekday = value
2681
2682proc `yearday=`*(dt: var DateTime, value: YeardayRange) {.deprecated: "Deprecated since v1.3.1".} =
2683  dt.yearday = value
2684
2685proc `isDst=`*(dt: var DateTime, value: bool) {.deprecated: "Deprecated since v1.3.1".} =
2686  dt.isDst = value
2687
2688proc `timezone=`*(dt: var DateTime, value: Timezone) {.deprecated: "Deprecated since v1.3.1".} =
2689  dt.timezone = value
2690
2691proc `utcOffset=`*(dt: var DateTime, value: int) {.deprecated: "Deprecated since v1.3.1".} =
2692  dt.utcOffset = value
2693