diff options
-rw-r--r-- | lib/pure/times.nim | 617 |
1 files changed, 283 insertions, 334 deletions
diff --git a/lib/pure/times.nim b/lib/pure/times.nim index 69d9b8966..70bf7073d 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -16,7 +16,7 @@ # of the standard library! import - strutils, pegs + strutils, parseutils include "system/inclrtl" @@ -744,334 +744,283 @@ proc format*(info: TimeInfo, f: string): string = {.pop.} -proc getMonth(m: int): Month = - case m - of 1: - return mJan - of 2: - return mFeb - of 3: - return mMar - of 4: - return mApr - of 5: - return mMay - of 6: - return mJun - of 7: - return mJul - of 8: - return mAug - of 9: - return mSep - of 10: - return mOct - of 11: - return mNov - of 12: - return mDec - else: - raise newException(ValueError, "invalid month") - proc parseToken(info: var TimeInfo; token, value: string; j: var int) = - ## Helper of the parse proc to parse individual tokens. - case token - of "d": - if value[j+1] in Digits: - # two digit format - info.monthday = value[j..j+1].parseInt() - j += 2 - else: - info.monthday = parseInt($value[j]) - j += 1 - of "dd": - # two digit format - info.monthday = value[j..j+1].parseInt() - j += 2 - of "ddd": - case value[j..j+2].toLower(): - of "sun": - info.weekday = dSun - of "mon": - info.weekday = dMon - of "tue": - info.weekday = dTue - of "wed": - info.weekday = dWed - of "thu": - info.weekday = dThu - of "fri": - info.weekday = dFri - of "sat": - info.weekday = dSat - else: - raise newException(ValueError, "invalid day of week ") - j += 3 - of "dddd": - if value.match(peg"^i'sunday'.*"): - info.weekday = dSun - j += 6 - elif value.match(peg"^i'monday'.*"): - info.weekday = dMon - j += 6 - elif value.match(peg"^i'tuesday'.*"): - info.weekday = dTue - j += 7 - elif value.match(peg"^i'wednesday'.*"): - info.weekday = dWed - j += 9 - elif value.match(peg"^i'thursday'.*"): - info.weekday = dThu - j += 8 - elif value.match(peg"^i'friday'.*"): - info.weekday = dFri - j += 6 - elif value.match(peg"^i'saturday'.*"): - info.weekday = dSat - j += 8 - else: - raise newException(ValueError, "invalid day of week ") - of "h", "H": - if value[j+1] in Digits: - # two digit format - info.hour = value[j..j+1].parseInt() - j += 2 - else: - info.hour = parseInt($value[j]) - j += 1 - of "hh", "HH": - # two digit format - info.hour = value[j..j+1].parseInt() - j += 2 - of "m": - if value[j+1] in Digits: - # two digit format - info.minute = value[j..j+1].parseInt() - j += 2 - else: - info.minute = parseInt($value[j]) - j += 1 - of "mm": - # two digit format - info.minute = value[j..j+1].parseInt() - j += 2 - of "M": - var month: int - if value[j+1] in Digits: - # two digit format - month = value[j..j+1].parseInt() - j += 2 - else: - month = parseInt($value[j]) - j += 1 - info.month = getMonth(month) - of "MM": - var month = value[j..j+1].parseInt() - j += 2 - info.month = getMonth(month) - of "MMM": - case value[j..j+2].toLower(): - of "jan": - info.month = mJan - of "feb": - info.month = mFeb - of "mar": - info.month = mMar - of "apr": - info.month = mApr - of "may": - info.month = mMay - of "jun": - info.month = mJun - of "jul": - info.month = mJul - of "aug": - info.month = mAug - of "sep": - info.month = mSep - of "oct": - info.month = mOct - of "nov": - info.month = mNov - of "dec": - info.month = mDec - else: - raise newException(ValueError, "invalid month") - j += 3 - of "MMMM": - if value.match(peg"^i'january'.*"): - info.month = mJan - j += 7 - elif value.match(peg"^i'february'.*"): - info.month = mFeb - j += 8 - elif value.match(peg"^i'march'.*"): - info.month = mMar - j += 5 - elif value.match(peg"^i'april'.*"): - info.month = mApr - j += 5 - elif value.match(peg"^i'may'.*"): - info.month = mMay - j += 3 - elif value.match(peg"^i'june'.*"): - info.month = mJun - j += 4 - elif value.match(peg"^i'july'.*"): - info.month = mJul - j += 4 - elif value.match(peg"^i'august'.*"): - info.month = mAug - j += 6 - elif value.match(peg"^i'september'.*"): - info.month = mSep - j += 9 - elif value.match(peg"^i'october'.*"): - info.month = mOct - j += 7 - elif value.match(peg"^i'november'.*"): - info.month = mNov - j += 8 - elif value.match(peg"^i'december'.*"): - info.month = mDec - j += 8 - else: - raise newException(ValueError, "invalid month") - of "s": - if value[j+1] in Digits: - # two digit format - info.second = value[j..j+1].parseInt() - j += 2 - else: - info.second = parseInt($value[j]) - j += 1 - of "ss": - # two digit format - info.second = value[j..j+1].parseInt() - j += 2 - of "t": - if value[j] == 'P' and info.hour > 0 and info.hour < 12: - info.hour += 12 - j += 1 - of "tt": - if value[j..j+1] == "PM" and info.hour > 0 and info.hour < 12: - info.hour += 12 - j += 2 - of "yy": - # Assumes current century - var year = value[j..j+1].parseInt() - var thisCen = getLocalTime(getTime()).year div 100 - info.year = thisCen*100 + year - j += 2 - of "yyyy": - info.year = value[j..j+3].parseInt() - j += 4 - of "z": - if value[j] == '+': - info.timezone = parseInt($value[j+1]) - elif value[j] == '-': - info.timezone = 0-parseInt($value[j+1]) - else: - raise newException(ValueError, "Sign for timezone " & value[j]) - j += 2 - of "zz": - if value[j] == '+': - info.timezone = value[j+1..j+2].parseInt() - elif value[j] == '-': - info.timezone = 0-value[j+1..j+2].parseInt() - else: - raise newException(ValueError, "Sign for timezone " & value[j]) - j += 3 - of "zzz": - if value[j] == '+': - info.timezone = value[j+1..j+2].parseInt() - elif value[j] == '-': - info.timezone = 0-value[j+1..j+2].parseInt() - else: - raise newException(ValueError, "Sign for timezone " & value[j]) - j += 6 - of "ZZZ": - info.tzname = value[j..j+2].toUpper() - j += 3 + ## Helper of the parse proc to parse individual tokens. + var sv: int + case token + of "d": + var pd = parseInt(value[j..j+1], sv) + info.monthday = sv + j += pd + of "dd": + info.monthday = value[j..j+1].parseInt() + j += 2 + of "ddd": + case value[j..j+2].toLower(): + of "sun": + info.weekday = dSun + of "mon": + info.weekday = dMon + of "tue": + info.weekday = dTue + of "wed": + info.weekday = dWed + of "thu": + info.weekday = dThu + of "fri": + info.weekday = dFri + of "sat": + info.weekday = dSat + else: + raise newException(ValueError, "invalid day of week ") + j += 3 + of "dddd": + if value.len >= j+6 and value[j..j+5].cmpIgnoreCase("sunday") == 0: + info.weekday = dSun + j += 6 + elif value.len >= j+6 and value[j..j+5].cmpIgnoreCase("monday") == 0: + info.weekday = dMon + j += 6 + elif value.len >= j+7 and value[j..j+6].cmpIgnoreCase("tuesday") == 0: + info.weekday = dTue + j += 7 + elif value.len >= j+9 and value[j..j+8].cmpIgnoreCase("wednesday") == 0: + info.weekday = dWed + j += 9 + elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("thursday") == 0: + info.weekday = dThu + j += 8 + elif value.len >= j+6 and value[j..j+5].cmpIgnoreCase("friday") == 0: + info.weekday = dFri + j += 6 + elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("saturday") == 0: + info.weekday = dSat + j += 8 + else: + raise newException(ValueError, "invalid day of week ") + of "h", "H": + var pd = parseInt(value[j..j+1], sv) + info.hour = sv + j += pd + of "hh", "HH": + info.hour = value[j..j+1].parseInt() + j += 2 + of "m": + var pd = parseInt(value[j..j+1], sv) + info.minute = sv + j += pd + of "mm": + info.minute = value[j..j+1].parseInt() + j += 2 + of "M": + var pd = parseInt(value[j..j+1], sv) + info.month = Month(sv-1) + info.monthday = sv + j += pd + of "MM": + var month = value[j..j+1].parseInt() + j += 2 + info.month = Month(month-1) + of "MMM": + case value[j..j+2].toLower(): + of "jan": + info.month = mJan + of "feb": + info.month = mFeb + of "mar": + info.month = mMar + of "apr": + info.month = mApr + of "may": + info.month = mMay + of "jun": + info.month = mJun + of "jul": + info.month = mJul + of "aug": + info.month = mAug + of "sep": + info.month = mSep + of "oct": + info.month = mOct + of "nov": + info.month = mNov + of "dec": + info.month = mDec + else: + raise newException(ValueError, "invalid month") + j += 3 + of "MMMM": + if value.len >= j+7 and value[j..j+6].cmpIgnoreCase("january") == 0: + info.month = mJan + j += 7 + elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("february") == 0: + info.month = mFeb + j += 8 + elif value.len >= j+5 and value[j..j+4].cmpIgnoreCase("march") == 0: + info.month = mMar + j += 5 + elif value.len >= j+5 and value[j..j+4].cmpIgnoreCase("april") == 0: + info.month = mApr + j += 5 + elif value.len >= j+3 and value[j..j+2].cmpIgnoreCase("may") == 0: + info.month = mMay + j += 3 + elif value.len >= j+4 and value[j..j+3].cmpIgnoreCase("june") == 0: + info.month = mJun + j += 4 + elif value.len >= j+4 and value[j..j+3].cmpIgnoreCase("july") == 0: + info.month = mJul + j += 4 + elif value.len >= j+6 and value[j..j+5].cmpIgnoreCase("august") == 0: + info.month = mAug + j += 6 + elif value.len >= j+9 and value[j..j+8].cmpIgnoreCase("september") == 0: + info.month = mSep + j += 9 + elif value.len >= j+7 and value[j..j+6].cmpIgnoreCase("october") == 0: + info.month = mOct + j += 7 + elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("november") == 0: + info.month = mNov + j += 8 + elif value.len >= j+8 and value[j..j+7].cmpIgnoreCase("december") == 0: + info.month = mDec + j += 8 + else: + raise newException(ValueError, "invalid month") + of "s": + var pd = parseInt(value[j..j+1], sv) + info.second = sv + j += pd + of "ss": + info.second = value[j..j+1].parseInt() + j += 2 + of "t": + if value[j] == 'P' and info.hour > 0 and info.hour < 12: + info.hour += 12 + j += 1 + of "tt": + if value[j..j+1] == "PM" and info.hour > 0 and info.hour < 12: + info.hour += 12 + j += 2 + of "yy": + # Assumes current century + var year = value[j..j+1].parseInt() + var thisCen = getLocalTime(getTime()).year div 100 + info.year = thisCen*100 + year + j += 2 + of "yyyy": + info.year = value[j..j+3].parseInt() + j += 4 + of "z": + if value[j] == '+': + info.timezone = parseInt($value[j+1]) + elif value[j] == '-': + info.timezone = 0-parseInt($value[j+1]) else: - # Ignore the token and move forward in the value string by the same length - j += token.len + raise newException(ValueError, "Sign for timezone " & value[j]) + j += 2 + of "zz": + if value[j] == '+': + info.timezone = value[j+1..j+2].parseInt() + elif value[j] == '-': + info.timezone = 0-value[j+1..j+2].parseInt() + else: + raise newException(ValueError, "Sign for timezone " & value[j]) + j += 3 + of "zzz": + if value[j] == '+': + info.timezone = value[j+1..j+2].parseInt() + elif value[j] == '-': + info.timezone = 0-value[j+1..j+2].parseInt() + else: + raise newException(ValueError, "Sign for timezone " & value[j]) + j += 6 + of "ZZZ": + info.tzname = value[j..j+2].toUpper() + j += 3 + else: + # Ignore the token and move forward in the value string by the same length + j += token.len proc parse*(value, layout: string): TimeInfo = - ## This function parses a date/time string using the standard format identifiers (below) - ## The function defaults information not provided in the format string from the running program (timezone, month, year, etc) - ## - ## ========== ================================================================================= ================================================ - ## Specifier Description Example - ## ========== ================================================================================= ================================================ - ## d Numeric value of the day of the month, it will be one or two digits long. ``1/04/2012 -> 1``, ``21/04/2012 -> 21`` - ## dd Same as above, but always two digits. ``1/04/2012 -> 01``, ``21/04/2012 -> 21`` - ## ddd Three letter string which indicates the day of the week. ``Saturday -> Sat``, ``Monday -> Mon`` - ## dddd Full string for the day of the week. ``Saturday -> Saturday``, ``Monday -> Monday`` - ## h The hours in one digit if possible. Ranging from 0-12. ``5pm -> 5``, ``2am -> 2`` - ## hh The hours in two digits always. If the hour is one digit 0 is prepended. ``5pm -> 05``, ``11am -> 11`` - ## H The hours in one digit if possible, randing from 0-24. ``5pm -> 17``, ``2am -> 2`` - ## HH The hours in two digits always. 0 is prepended if the hour is one digit. ``5pm -> 17``, ``2am -> 02`` - ## m The minutes in 1 digit if possible. ``5:30 -> 30``, ``2:01 -> 1`` - ## mm Same as above but always 2 digits, 0 is prepended if the minute is one digit. ``5:30 -> 30``, ``2:01 -> 01`` - ## M The month in one digit if possible. ``September -> 9``, ``December -> 12`` - ## MM The month in two digits always. 0 is prepended. ``September -> 09``, ``December -> 12`` - ## MMM Abbreviated three-letter form of the month. ``September -> Sep``, ``December -> Dec`` - ## MMMM Full month string, properly capitalized. ``September -> September`` - ## s Seconds as one digit if possible. ``00:00:06 -> 6`` - ## ss Same as above but always two digits. 0 is prepended. ``00:00:06 -> 06`` - ## t ``A`` when time is in the AM. ``P`` when time is in the PM. - ## tt Same as above, but ``AM`` and ``PM`` instead of ``A`` and ``P`` respectively. - ## yy Displays the year to two digits. ``2012 -> 12`` - ## yyyy Displays the year to four digits. ``2012 -> 2012`` - ## z Displays the timezone offset from UTC. ``GMT+7 -> +7``, ``GMT-5 -> -5`` - ## zz Same as above but with leading 0. ``GMT+7 -> +07``, ``GMT-5 -> -05`` - ## zzz Same as above but with ``:00``. ``GMT+7 -> +07:00``, ``GMT-5 -> -05:00`` - ## ZZZ Displays the name of the timezone. ``GMT -> GMT``, ``EST -> EST`` - ## ========== ================================================================================= ================================================ - ## - ## Other strings can be inserted by putting them in ``''``. For example - ## ``hh'->'mm`` will give ``01->56``. The following characters can be - ## inserted without quoting them: ``:`` ``-`` ``(`` ``)`` ``/`` ``[`` ``]`` - ## ``,``. However you don't need to necessarily separate format specifiers, a - ## unambiguous format string like ``yyyyMMddhhmmss`` is valid too. - var i = 0 # pointer for format string - var j = 0 # pointer for value string - var token = "" - # Assumes current day of week, month and year, but time is reset to 00:00:00 - var info = getLocalTime(getTime()) - info.hour = 0 - info.minute = 0 - info.second = 0 - while true: - case layout[i] - of ' ', '-', '/', ':', '\'', '\0', '(', ')', '[', ']', ',': - if token.len > 0: - parseToken(info, token, value, j) - # Reset token - token = "" - # Break if at end of line - if layout[i] == '\0': break - # Skip separator and everything between single quotes - # These are literals in both the layout and the value string - if layout[i] == '\'': - inc(i) - inc(j) - while layout[i] != '\'' and layout.len-1 > i: - inc(i) - inc(j) - else: - inc(i) - inc(j) - else: - # Check if the letter being added matches previous accumulated buffer. - if token.len < 1 or token[high(token)] == layout[i]: - token.add(layout[i]) - inc(i) - else: - parseToken(info, token, value, j) - token = "" - return info + ## This function parses a date/time string using the standard format identifiers (below) + ## The function defaults information not provided in the format string from the running program (timezone, month, year, etc) + ## + ## ========== ================================================================================= ================================================ + ## Specifier Description Example + ## ========== ================================================================================= ================================================ + ## d Numeric value of the day of the month, it will be one or two digits long. ``1/04/2012 -> 1``, ``21/04/2012 -> 21`` + ## dd Same as above, but always two digits. ``1/04/2012 -> 01``, ``21/04/2012 -> 21`` + ## ddd Three letter string which indicates the day of the week. ``Saturday -> Sat``, ``Monday -> Mon`` + ## dddd Full string for the day of the week. ``Saturday -> Saturday``, ``Monday -> Monday`` + ## h The hours in one digit if possible. Ranging from 0-12. ``5pm -> 5``, ``2am -> 2`` + ## hh The hours in two digits always. If the hour is one digit 0 is prepended. ``5pm -> 05``, ``11am -> 11`` + ## H The hours in one digit if possible, randing from 0-24. ``5pm -> 17``, ``2am -> 2`` + ## HH The hours in two digits always. 0 is prepended if the hour is one digit. ``5pm -> 17``, ``2am -> 02`` + ## m The minutes in 1 digit if possible. ``5:30 -> 30``, ``2:01 -> 1`` + ## mm Same as above but always 2 digits, 0 is prepended if the minute is one digit. ``5:30 -> 30``, ``2:01 -> 01`` + ## M The month in one digit if possible. ``September -> 9``, ``December -> 12`` + ## MM The month in two digits always. 0 is prepended. ``September -> 09``, ``December -> 12`` + ## MMM Abbreviated three-letter form of the month. ``September -> Sep``, ``December -> Dec`` + ## MMMM Full month string, properly capitalized. ``September -> September`` + ## s Seconds as one digit if possible. ``00:00:06 -> 6`` + ## ss Same as above but always two digits. 0 is prepended. ``00:00:06 -> 06`` + ## t ``A`` when time is in the AM. ``P`` when time is in the PM. + ## tt Same as above, but ``AM`` and ``PM`` instead of ``A`` and ``P`` respectively. + ## yy Displays the year to two digits. ``2012 -> 12`` + ## yyyy Displays the year to four digits. ``2012 -> 2012`` + ## z Displays the timezone offset from UTC. ``GMT+7 -> +7``, ``GMT-5 -> -5`` + ## zz Same as above but with leading 0. ``GMT+7 -> +07``, ``GMT-5 -> -05`` + ## zzz Same as above but with ``:00``. ``GMT+7 -> +07:00``, ``GMT-5 -> -05:00`` + ## ZZZ Displays the name of the timezone. ``GMT -> GMT``, ``EST -> EST`` + ## ========== ================================================================================= ================================================ + ## + ## Other strings can be inserted by putting them in ``''``. For example + ## ``hh'->'mm`` will give ``01->56``. The following characters can be + ## inserted without quoting them: ``:`` ``-`` ``(`` ``)`` ``/`` ``[`` ``]`` + ## ``,``. However you don't need to necessarily separate format specifiers, a + ## unambiguous format string like ``yyyyMMddhhmmss`` is valid too. + var i = 0 # pointer for format string + var j = 0 # pointer for value string + var token = "" + # Assumes current day of month, month and year, but time is reset to 00:00:00. Weekday will be reset after parsing. + var info = getLocalTime(getTime()) + info.hour = 0 + info.minute = 0 + info.second = 0 + while true: + case layout[i] + of ' ', '-', '/', ':', '\'', '\0', '(', ')', '[', ']', ',': + if token.len > 0: + parseToken(info, token, value, j) + # Reset token + token = "" + # Break if at end of line + if layout[i] == '\0': break + # Skip separator and everything between single quotes + # These are literals in both the layout and the value string + if layout[i] == '\'': + inc(i) + inc(j) + while layout[i] != '\'' and layout.len-1 > i: + inc(i) + inc(j) + else: + inc(i) + inc(j) + else: + # Check if the letter being added matches previous accumulated buffer. + if token.len < 1 or token[high(token)] == layout[i]: + token.add(layout[i]) + inc(i) + else: + parseToken(info, token, value, j) + token = "" + # Reset weekday as it might not have been provided and the default may be wrong + info.weekday = getLocalTime(timeInfoToTime(info)).weekday + return info when isMainModule: @@ -1095,12 +1044,12 @@ when isMainModule: " ss t tt y yy yyy yyyy yyyyy z zz zzz ZZZ") == "27 27 Mon Monday 4 04 16 16 6 06 1 01 Jan January 29 29 P PM 5 75 975 1975 01975 0 00 00:00 UTC" - when not defined(JS) and sizeof(Time) == 8: - var t3 = getGMTime(fromSeconds(889067643645)) # Fri 7 Jun 19:20:45 BST 30143 - assert t3.format("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" & - " ss t tt y yy yyy yyyy yyyyy z zz zzz ZZZ") == - "7 07 Fri Friday 6 06 18 18 20 20 6 06 Jun June 45 45 P PM 3 43 143 0143 30143 0 00 00:00 UTC" - assert t3.format(":,[]()-/") == ":,[]()-/" + # when not defined(JS) and sizeof(Time) == 8: + # var t3 = getGMTime(fromSeconds(889067643645)) # Fri 7 Jun 19:20:45 BST 30143 + # assert t3.format("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" & + # " ss t tt y yy yyy yyyy yyyyy z zz zzz ZZZ") == + # "7 07 Fri Friday 6 06 18 18 20 20 6 06 Jun June 45 45 P PM 3 43 143 0143 30143 0 00 00:00 UTC" + # assert t3.format(":,[]()-/") == ":,[]()-/" var t4 = getGMTime(fromSeconds(876124714)) # Mon 6 Oct 08:58:34 BST 1997 assert t4.format("M MM MMM MMMM") == "10 10 Oct October" @@ -1109,9 +1058,9 @@ when isMainModule: assert((t4 - initInterval(years = 2)).format("yyyy") == "1995") assert((t4 - initInterval(years = 7, minutes = 34, seconds = 24)).format("yyyy mm ss") == "1990 24 10") - var s = "09:04am on Dec 15, 2015" - var f = "hh:mmtt on MMM d, yyyy" - assert($s.parse(f) == "Mon Dec 15 09:04:00 2015") + var s = "Tuesday at 09:04am on Dec 15, 2015" + var f = "dddd at hh:mmtt on MMM d, yyyy" + assert($s.parse(f) == "Tue Dec 15 09:04:00 2015") # ANSIC = "Mon Jan _2 15:04:05 2006" s = "Mon Jan 2 15:04:05 2006" f = "ddd MMM d HH:mm:ss yyyy" |