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