Chrono a Timestamps, Calendars, and Timezones library for nim.
Parse timestamps:
var ts = parseTs( "{year/4}-{month/2}-{day/2}T{hour/2}:{minute/2}:{second/2}Z", "1988-02-09T03:34:12Z" )
Format timestamps:
echo formatTs( ts, "{year/4}-{month/2}-{day/2}T{hour/2}:{minute/2}:{second/2}Z", )
Manipulate Calendars:
var cal = Calendar(year: 2013, month: 12, day: 31, hour: 59, minute: 59, second: 59) cal.addSeconds(20) cal.subMinutes(15) cal.addDays(40) cal.subMonths(120)
Use timezones:
echo formatTs( ts, "{year/4}-{month/2}-{day/2}T{hour/2}:{minute/2}:{second/2}Z", tzName = "America/Los_Angeles" )
Format Specification
Specifier | Description | Example |
---|---|---|
{year} | Year in as many digits as needed. Can be negative. | 12012/9/3 -> 12012 |
{year/2} | Two digit year, 0-30 represents 2000-2030 while 30-99 is 1930 to 1999. | 2012/9/3 -> 12 |
{year/4} | Four digits of the year. Years 0 - 9999. | 2012/9/3 -> 2012 |
{quarter} | Always 1 digit. 1-4 | 2012/9/4 -> 3 |
{month} | Month in digits 1-12 | 2012/9/3 -> 9 |
{month/2} | Month in two digits 01-12 | 2012/9/3 -> 09 |
{month/n} | Full name of month | September -> September |
{month/n/3} | Three letter name of month | September -> Sep |
{day} | Day in digits 1-31 | 2012/9/3 -> 3 |
{day/2} | Day in two digits 01-31 | 2012/9/3 -> 03 |
{hour} | Hour in digits 0-23 | 09:08:07 -> 9 |
{hour/2} | Hour in two digits 00-23 | 09:08:07 -> 09 |
{hour/ap} | Hour as 12-hour am/pm as digits 1-12 | 13:08:07 -> 1 |
{hour/2/ap} | Hour as 12-hour am/pm as two digits 01-12 | 13:08:07 -> 01 |
{am/pm} | Based on hour outputs "am" or "pm" | 13:08:07 -> pm |
{minute} | Minute in digits 0-59 | 09:08:07 -> 8 |
{minute/2} | Minute in two digits 00-59 | 09:08:07 -> 08 |
{second} | Second in digits 0-59 | 09:08:07 -> 7 |
{second/2} | Second in two digits 00-59 | 09:08:07 -> 07 |
{secondFraction} | Seconds fraction value. | 09:08:07.123456789123 -> 0.123456789123 |
{secondFraction/2} | Seconds fraction 2 digit value. | 09:08:07.12 -> 0.12 |
{secondFraction/7} | Seconds fraction 7 digit value. | 09:08:07.1234567 -> 0.1234567 |
{weekday} | Full name of weekday | Saturday -> Saturday |
{weekday/3} | Three letter of name of weekday | Saturday -> Sat |
{weekday/2} | Two letter of name of weekday | Saturday -> Sa |
{tzName} | Timezone name (can't be parsed) | America/Los_Angeles |
{dstName} | Daylight savings name or standard name (can't be parsed) | PDT |
{offsetDir} | Which direction is the offset going | + or - |
{offsetHour/2} | Offset hour 00-12 | 07 |
{offsetMinute/2} | Offset minute 00-58 | 00 |
{offsetSecond/2} | Offset second 00-58 | 00 |
Any string that is not in {} considered to not be part of the format and is just inserted. "{year/4} and {month/2} and {day/2}" -> "1988 and 02 and 09"
Common Formats
American Date: 01/12/2020 "{month/2}/{day/2}/{year/4}"
Europian Date: 01.12.2020 "{year/4}.{month/2}.{day/2}"
American Time: 8:12pm "{hour/2/ap}:{minute/2}"
Europian Time: 20:12 "{hour/2}:{minute/2}"
ISO with fractional seconds: 2020-01-12T02:00:33.387824Z "{year/4}-{month/2}-{day/2}T{hour/2}:{minute/2}:{second}.{secondFraction/7}Z" parseIso/formatIso are faster but fractional seconds.
chrono/calendars
Calendars are more involved they support more features but come with complexity and are mutable.
I do not recommend storing Calendar objects in files or databases. Store Timestamp instead.
Most useful thing about calendars is that you can add years, months, days, hours, minutes or seconds to them.
var cal = Calendar(year: 2013, month: 12, day: 31, hour: 59, minute: 59, second: 59) cal.add(Second, 20) cal.sub(Minute, 15) cal.add(Day, 40) cal.sub(Month, 120) cal.toStartOf(Day) cal.toEndOf(Month)
It supports same format specification as Timestamp:
echo cal.formatCalendar("{month/2}/{day/2}/{year/4} {hour/2}:{minute/2}:{second/2}")
If you need extra features that calendars provide I recommending creating a Calendar with tsToCalendar doing your work and converting back with calendarToTs.
ts = cal.ts cal = ts.calendar
chrono/timestamps
If you are going to just parse or format dates. I recommend using just the import chrono/timestamps module. It it imports the Timestamp that is enough for most cases involved with times. I always recommend storing dates as a float64 number of seconds since 1970. This is exactly what Timestamp is. When you need to parse it or display it use parseTs or formatTs.
var ts = parseTs( "{year/4}-{month/2}-{day/2}T{hour/2}:{minute/2}:{second/2}Z", "1988-02-09T03:34:12Z" )
echo ts
echo formatTs( ts, "{year/4}-{month/2}-{day/2}T{hour/2}:{minute/2}:{second/2}Z", )
If you need to parse ISO dates which is a very common format you find all over the internet. You can even use faster optimized versions here:
echo parseIsoTs("2017-11-08T08:01:43Z") echo Timestamp(1510128103.0).formatIso()
chrono/timezones
Timezones can be complicated. But if you treat them as a presentation level issue sort of like language it becomes easier. Never store anything as non-UTC. If you need to store timezone info store it as a string plus a Timestamp. When you need to display or parse it use the tzName attribute.
var ts = parseTs( "{year/4}-{month/2}-{day/2}T{hour/2}:{minute/2}:{second/2}Z", "1988-02-09T03:34:12Z", tzName = "America/Los_Angeles" ) echo ts echo formatTs( ts, "{year/4}-{month/2}-{day/2}T{hour/2}:{minute/2}:{second/2}Z", tzName = "America/Los_Angeles" )
Timezone and daylight savings can and do change unpredictably remember to keep this library up to date.
When you import the library it also statically includes compressed daylight savings table in the binary which is about 0.7MB. It does not use OS's timezone functions. This way, you are always guaranteed to get the same result on all platforms.
Many confuse proper time zone names like "America/Los_Angeles" with 3-4 letter time zone abbreviations like "PST" or "PDT". Time zone abbreviations cannot describe a time zone fully, unless you know what country you are in and its a common one. It is always recommended to use full timezone names for parsing and storage and only display time zone abbreviations and never parse them.
Vars
dstChanges: seq[DstChange]
- List of all DST changes Source Edit
Procs
proc add(cal: var Calendar; timeScale: TimeScale; number: float) {....raises: [], tags: [], forbids: [].}
- Source Edit
proc applyTimezone(cal: var Calendar; tzName: string) {....raises: [Exception], tags: [RootEffect], forbids: [].}
- take a calendar and apply a timezone to it this does not changes timestamp of the calendar but does change the hour:minute Source Edit
proc calendar(ts: Timestamp): Calendar {....raises: [], tags: [], forbids: [].}
- Converts a Timestamp to a Calendar. Source Edit
proc clearTimezone(cal: var Calendar) {....raises: [], tags: [], forbids: [].}
- Removes timezone form calendar Source Edit
proc daysInMonth(cal: Calendar): int {....raises: [], tags: [], forbids: [].}
- Get number of days in a calendar month. Source Edit
proc findTimeZone(tzId: int): TimeZone {....raises: [], tags: [], forbids: [].}
- Finds timezone by its id (slow). Source Edit
proc findTimeZone(tzName: string): TimeZone {....raises: [Exception], tags: [RootEffect], forbids: [].}
- Finds timezone by its name Source Edit
proc format(cal: Calendar; format: string): string {....raises: [ValueError], tags: [], forbids: [].}
- Formats calendars to a string based on the format specification. Source Edit
proc formatIso(cal: Calendar): string {....raises: [], tags: [], forbids: [].}
- Fastest way to convert Calendar to an ISO 8601 string representation. Use this instead of the format function when dealing with ISO format. Warning does minimal checking for speed. Make sure your calendar is valid. Note: Does not support fractional seconds. Source Edit
proc formatIso(ts: Timestamp): string {....raises: [], tags: [], forbids: [].}
- Fastest way to convert Timestamp to an ISO 8601 string representation. Use this instead of the format function when dealing with ISO format. Source Edit
proc loadTzData(tzData: string) {....raises: [IOError, OSError, JsonParsingError, ValueError, KeyError, JsonKindError], tags: [ReadIOEffect, WriteIOEffect, RootEffect], forbids: [].}
-
Loads timezone information from tzdata.json file contents. To statically include time zones in your program:
loadTzData(staticRead("your-path/tzdata.json"))
Source Edit proc normalizeTimezone(cal: var Calendar) {....raises: [Exception], tags: [RootEffect], forbids: [].}
- After shifting around the calendar, its DST might need to be updated. Source Edit
proc parseCalendar(format: string; value: string): Calendar {. ...raises: [ValueError], tags: [], forbids: [].}
- Parses calendars from a string based on the format specification. Note that not all valid formats can be parsed, things such as weekdays or am/pm stuff without hours. Source Edit
proc parseCalendar(formats: seq[string]; value: string): Calendar {. ...raises: [ValueError], tags: [], forbids: [].}
- Parses calendars from a seq of strings based on the format specification. Returns first format that parses or rases ValueError. Source Edit
proc parseIsoCalendar(iso: string): Calendar {....raises: [ValueError], tags: [], forbids: [].}
- Fastest way to convert an ISO 8601 string representation to a Calendar. Use this instead of the parseTimestamp function when dealing with ISO format. Note: Does not support fractional seconds. Source Edit
proc parseIsoTs(iso: string): Timestamp {....raises: [ValueError], tags: [], forbids: [].}
- Fastest way to convert an ISO 8601 string representation to a Timestamp. Use this instead of the parseTimestamp function when dealing with ISO format. Source Edit
proc parseTimeScale(timeScale: string): TimeScale {....raises: [], tags: [], forbids: [].}
- Turns a time scale string like "second" to the enum Second. Source Edit
proc shiftTimezone(cal: var Calendar; tzName: string) {....raises: [Exception], tags: [RootEffect], forbids: [].}
- take a calendar and moves it into a timezone this does changes timestamp of the calendar but does not change the hour:minute Source Edit
proc sub(cal: var Calendar; timeScale: TimeScale; number: float) {....raises: [], tags: [], forbids: [].}
- Subtract a Day, Hour, Year... to calendar. Source Edit
Iterators
iterator findDstChanges(tz: TimeZone): DstChange {....raises: [Exception], tags: [RootEffect], forbids: [].}
- Finds timezone dst changes by timezone. Source Edit
iterator findTimeZoneFromDstName(dstName: string): TimeZone {....raises: [], tags: [], forbids: [].}
- Finds timezones by its dst name (slow). Source Edit