summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--build_all.sh4
-rw-r--r--changelog.md2
-rw-r--r--compiler/ccgexprs.nim10
-rw-r--r--compiler/ccgstmts.nim31
-rw-r--r--compiler/packagehandling.nim4
-rw-r--r--compiler/types.nim17
-rw-r--r--lib/pure/collections/tables.nim11
-rw-r--r--lib/pure/net.nim12
-rw-r--r--lib/pure/times.nim148
-rw-r--r--testament/categories.nim21
-rw-r--r--testament/specs.nim7
-rw-r--r--testament/tester.nim57
-rw-r--r--tests/errmsgs/tuncheckedarrayvar.nim9
-rw-r--r--tests/macros/tmacros_issues.nim4
-rw-r--r--tests/stdlib/ttimes.nim27
15 files changed, 202 insertions, 162 deletions
diff --git a/build_all.sh b/build_all.sh
index 333814d62..e22facc1b 100644
--- a/build_all.sh
+++ b/build_all.sh
@@ -17,14 +17,14 @@ build_nim_csources(){
   ## avoid changing dir in case of failure
   (
     echo_run cd csources
-    echo_run sh build.sh
+    echo_run sh build.sh $@
   )
   # keep $nim_csources in case needed to investigate bootstrap issues
   # without having to rebuild from csources
   echo_run cp bin/nim $nim_csources
 }
 
-[ -f $nim_csources ] || echo_run build_nim_csources
+[ -f $nim_csources ] || echo_run build_nim_csources $@
 
 # Note: if fails, may need to `cd csources && git pull`
 echo_run bin/nim c --skipUserCfg --skipParentCfg koch
diff --git a/changelog.md b/changelog.md
index adc35a079..91b8f5bbd 100644
--- a/changelog.md
+++ b/changelog.md
@@ -193,6 +193,8 @@ proc enumToString*(enums: openArray[enum]): string =
 
 - The switch ``-d:useWinAnsi`` is not supported anymore.
 
+- In `times` module, procs `format` and `parse` accept a new optional `DateTimeLocale` argument for formatting/parsing dates in other languages.
+
 
 ### Language additions
 
diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim
index 97ce5a485..ec32f5975 100644
--- a/compiler/ccgexprs.nim
+++ b/compiler/ccgexprs.nim
@@ -718,14 +718,10 @@ proc isCppRef(p: BProc; typ: PType): bool {.inline.} =
       tfVarIsPtr notin skipTypes(typ, abstractInstOwned).flags
 
 proc genDeref(p: BProc, e: PNode, d: var TLoc) =
-  let
-    enforceDeref = lfEnforceDeref in d.flags
-    mt = mapType(p.config, e.sons[0].typ)
-  if mt in {ctArray, ctPtrToArray} and not enforceDeref:
+  let mt = mapType(p.config, e.sons[0].typ)
+  if mt in {ctArray, ctPtrToArray} and lfEnforceDeref notin d.flags:
     # XXX the amount of hacks for C's arrays is incredible, maybe we should
     # simply wrap them in a struct? --> Losing auto vectorization then?
-    #if e[0].kind != nkBracketExpr:
-    #  message(e.info, warnUser, "CAME HERE " & renderTree(e))
     expr(p, e.sons[0], d)
     if e.sons[0].typ.skipTypes(abstractInstOwned).kind == tyRef:
       d.storage = OnHeap
@@ -762,7 +758,7 @@ proc genDeref(p: BProc, e: PNode, d: var TLoc) =
            e.kind == nkHiddenDeref:
         putIntoDest(p, d, e, rdLoc(a), a.storage)
         return
-    if enforceDeref and mt == ctPtrToArray:
+    if mt == ctPtrToArray and lfEnforceDeref in d.flags:
       # we lie about the type for better C interop: 'ptr array[3,T]' is
       # translated to 'ptr T', but for deref'ing this produces wrong code.
       # See tmissingderef. So we get rid of the deref instead. The codegen
diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim
index 3be31503a..7552f0c63 100644
--- a/compiler/ccgstmts.nim
+++ b/compiler/ccgstmts.nim
@@ -83,18 +83,6 @@ proc genVarTuple(p: BProc, n: PNode) =
     # check with the boolean if the initializing code for the tuple should be ran
     lineCg(p, cpsStmts, "if ($1)$n", [hcrCond])
     startBlock(p)
-  defer:
-    if forHcr:
-      # end the block where the tuple gets initialized
-      endBlock(p)
-    if forHcr or isGlobalInBlock:
-      # insert the registration of the globals for the different parts of the tuple at the
-      # start of the current scope (after they have been iterated) and init a boolean to
-      # check if any of them is newly introduced and the initializing code has to be ran
-      lineCg(p, cpsLocals, "NIM_BOOL $1 = NIM_FALSE;$n", [hcrCond])
-      for curr in hcrGlobals:
-        lineCg(p, cpsLocals, "$1 |= hcrRegisterGlobal($4, \"$2\", sizeof($3), $5, (void**)&$2);$N",
-               [hcrCond, curr.loc.r, rdLoc(curr.loc), getModuleDllPath(p.module, n.sons[0].sym), curr.tp])
 
   genLineDir(p, n)
   initLocExpr(p, n.sons[L-1], tup)
@@ -123,7 +111,18 @@ proc genVarTuple(p: BProc, n: PNode) =
     if forHcr or isGlobalInBlock:
       hcrGlobals.add((loc: v.loc, tp: if traverseProc == nil: ~"NULL" else: traverseProc))
 
-proc genDeref(p: BProc, e: PNode, d: var TLoc)
+  if forHcr:
+    # end the block where the tuple gets initialized
+    endBlock(p)
+  if forHcr or isGlobalInBlock:
+    # insert the registration of the globals for the different parts of the tuple at the
+    # start of the current scope (after they have been iterated) and init a boolean to
+    # check if any of them is newly introduced and the initializing code has to be ran
+    lineCg(p, cpsLocals, "NIM_BOOL $1 = NIM_FALSE;$n", [hcrCond])
+    for curr in hcrGlobals:
+      lineCg(p, cpsLocals, "$1 |= hcrRegisterGlobal($4, \"$2\", sizeof($3), $5, (void**)&$2);$N",
+              [hcrCond, curr.loc.r, rdLoc(curr.loc), getModuleDllPath(p.module, n.sons[0].sym), curr.tp])
+
 
 proc loadInto(p: BProc, le, ri: PNode, a: var TLoc) {.inline.} =
   if ri.kind in nkCallKinds and (ri.sons[0].kind != nkSym or
@@ -364,13 +363,11 @@ proc genSingleVar(p: BProc, a: PNode) =
     lineCg(targetProc, cpsStmts, "if (hcrRegisterGlobal($3, \"$1\", sizeof($2), $4, (void**)&$1))$N",
            [v.loc.r, rdLoc(v.loc), getModuleDllPath(p.module, v), traverseProc])
     startBlock(targetProc)
-  defer:
-    if forHcr:
-      endBlock(targetProc)
-
   if a.sons[2].kind != nkEmpty:
     genLineDir(targetProc, a)
     loadInto(targetProc, a.sons[0], a.sons[2], v.loc)
+  if forHcr:
+    endBlock(targetProc)
 
 proc genClosureVar(p: BProc, a: PNode) =
   var immediateAsgn = a.sons[2].kind != nkEmpty
diff --git a/compiler/packagehandling.nim b/compiler/packagehandling.nim
index 0e9331128..548668824 100644
--- a/compiler/packagehandling.nim
+++ b/compiler/packagehandling.nim
@@ -41,11 +41,11 @@ proc getPackageName*(conf: ConfigRef; path: string): string =
 proc fakePackageName*(conf: ConfigRef; path: AbsoluteFile): string =
   # foo/../bar becomes foo7_7bar
   result = relativeTo(path, conf.projectPath, '/').string.multiReplace(
-    {"/": "7", "..": "_", "7": "77", "_": "__"})
+    {"/": "7", "..": "_", "7": "77", "_": "__", ":": "8", "8": "88"})
 
 proc demaglePackageName*(path: string): string =
   result = path.multiReplace(
-    {"77": "7", "__": "_", "_7": "../", "7": "/"})
+    {"88": "8", "8": ":", "77": "7", "__": "_", "_7": "../", "7": "/"})
 
 proc withPackageName*(conf: ConfigRef; path: AbsoluteFile): AbsoluteFile =
   let x = getPackageName(conf, path.string)
diff --git a/compiler/types.nim b/compiler/types.nim
index b0a6a4c17..d56794d2d 100644
--- a/compiler/types.nim
+++ b/compiler/types.nim
@@ -1199,9 +1199,9 @@ proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind,
     case t2.kind
     of tyVar, tyLent:
       if taHeap notin flags: result = t2 # ``var var`` is illegal on the heap
-    of tyOpenArray:
+    of tyOpenArray, tyUncheckedArray:
       if kind != skParam: result = t
-      else: result = typeAllowedAux(marker, t2, kind, flags)
+      else: result = typeAllowedAux(marker, t2.sons[0], skParam, flags)
     else:
       if kind notin {skParam, skResult}: result = t
       else: result = typeAllowedAux(marker, t2, kind, flags)
@@ -1235,14 +1235,21 @@ proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind,
     result = nil
   of tyOrdinal:
     if kind != skParam: result = t
-  of tyGenericInst, tyDistinct, tyAlias, tyInferred, tyUncheckedArray:
+  of tyGenericInst, tyDistinct, tyAlias, tyInferred:
     result = typeAllowedAux(marker, lastSon(t), kind, flags)
   of tyRange:
     if skipTypes(t.sons[0], abstractInst-{tyTypeDesc}).kind notin
         {tyChar, tyEnum, tyInt..tyFloat128, tyUInt8..tyUInt32}: result = t
   of tyOpenArray, tyVarargs, tySink:
-    if kind != skParam: result = t
-    else: result = typeAllowedAux(marker, t.sons[0], skVar, flags)
+    if kind != skParam:
+      result = t
+    else:
+      result = typeAllowedAux(marker, t.sons[0], skVar, flags)
+  of tyUncheckedArray:
+    if kind != skParam and taHeap notin flags:
+      result = t
+    else:
+      result = typeAllowedAux(marker, lastSon(t), kind, flags)
   of tySequence, tyOpt:
     if t.sons[0].kind != tyEmpty:
       result = typeAllowedAux(marker, t.sons[0], skVar, flags+{taHeap})
diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim
index 76ca15b62..93ccb4058 100644
--- a/lib/pure/collections/tables.nim
+++ b/lib/pure/collections/tables.nim
@@ -2133,6 +2133,7 @@ type
     ## <#initCountTable,int>`_.
     data: seq[tuple[key: A, val: int]]
     counter: int
+    isSorted: bool
   CountTableRef*[A] = ref CountTable[A] ## Ref version of
     ## `CountTable<#CountTable>`_.
     ##
@@ -2209,12 +2210,14 @@ proc `[]`*[A](t: CountTable[A], key: A): int =
   ##   (key, value) pair in the table
   ## * `hasKey proc<#hasKey,CountTable[A],A>`_ for checking if a key
   ##   is in the table
+  assert(not t.isSorted, "CountTable must not be used after sorting")
   ctget(t, key, 0)
 
 proc mget*[A](t: var CountTable[A], key: A): var int =
   ## Retrieves the value at ``t[key]``. The value can be modified.
   ##
   ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised.
+  assert(not t.isSorted, "CountTable must not be used after sorting")
   get(t, key)
 
 proc `[]=`*[A](t: var CountTable[A], key: A, val: int) =
@@ -2224,6 +2227,7 @@ proc `[]=`*[A](t: var CountTable[A], key: A, val: int) =
   ## * `[] proc<#[],CountTable[A],A>`_ for retrieving a value of a key
   ## * `inc proc<#inc,CountTable[A],A,int>`_ for incrementing a
   ##   value of a key
+  assert(not t.isSorted, "CountTable must not be used after sorting")
   assert val >= 0
   let h = rawGet(t, key)
   if h >= 0:
@@ -2239,6 +2243,7 @@ proc inc*[A](t: var CountTable[A], key: A, val = 1) =
     a.inc('b', 10)
     doAssert a == toCountTable("aaabbbbbbbbbbb")
 
+  assert(not t.isSorted, "CountTable must not be used after sorting")
   var index = rawGet(t, key)
   if index >= 0:
     inc(t.data[index].val, val)
@@ -2280,6 +2285,7 @@ proc hasKey*[A](t: CountTable[A], key: A): bool =
   ## * `[] proc<#[],CountTable[A],A>`_ for retrieving a value of a key
   ## * `getOrDefault proc<#getOrDefault,CountTable[A],A,int>`_ to return
   ##   a custom value if the key doesn't exist
+  assert(not t.isSorted, "CountTable must not be used after sorting")
   result = rawGet(t, key) >= 0
 
 proc contains*[A](t: CountTable[A], key: A): bool =
@@ -2304,6 +2310,7 @@ proc len*[A](t: CountTable[A]): int =
 proc clear*[A](t: var CountTable[A]) =
   ## Resets the table so that it is empty.
   clearImpl()
+  t.isSorted = false
 
 func ctCmp[T](a, b: tuple[key: T, val: int]): int =
   result = system.cmp(a.val, b.val)
@@ -2312,7 +2319,7 @@ proc sort*[A](t: var CountTable[A], order = SortOrder.Descending) =
   ## Sorts the count table so that, by default, the entry with the
   ## highest counter comes first.
   ##
-  ## **This is destructive! You must not modify ``t`` afterwards!**
+  ## **WARNING:** This is destructive! Once sorted, you must not modify ``t`` afterwards!
   ##
   ## You can use the iterators `pairs<#pairs.i,CountTable[A]>`_,
   ## `keys<#keys.i,CountTable[A]>`_, and `values<#values.i,CountTable[A]>`_
@@ -2327,6 +2334,7 @@ proc sort*[A](t: var CountTable[A], order = SortOrder.Descending) =
     doAssert toSeq(a.values) == @[1, 1, 2, 2, 5]
 
   t.data.sort(cmp=ctCmp, order=order)
+  t.isSorted = true
 
 proc merge*[A](s: var CountTable[A], t: CountTable[A]) =
   ## Merges the second table into the first one (must be declared as `var`).
@@ -2336,6 +2344,7 @@ proc merge*[A](s: var CountTable[A], t: CountTable[A]) =
     a.merge(b)
     doAssert a == toCountTable("aaabbbccc")
 
+  assert(not s.isSorted, "CountTable must not be used after sorting")
   for key, value in t:
     s.inc(key, value)
 
diff --git a/lib/pure/net.nim b/lib/pure/net.nim
index f55d5d900..69a221a6c 100644
--- a/lib/pure/net.nim
+++ b/lib/pure/net.nim
@@ -1083,7 +1083,7 @@ proc waitFor(socket: Socket, waited: var float, timeout, size: int,
   ## For buffered sockets it can be as big as ``BufferSize``.
   ##
   ## If this function does not determine that there is data on the socket
-  ## within ``timeout`` ms, an ETimeout error will be raised.
+  ## within ``timeout`` ms, a TimeoutError error will be raised.
   result = 1
   if size <= 0: assert false
   if timeout == -1: return size
@@ -1138,7 +1138,7 @@ proc recv*(socket: Socket, data: var string, size: int, timeout = -1,
   ## lower than 0 is never returned.
   ##
   ## A timeout may be specified in milliseconds, if enough data is not received
-  ## within the time specified an TimeoutError exception will be raised.
+  ## within the time specified a TimeoutError exception will be raised.
   ##
   ## **Note**: ``data`` must be initialised.
   ##
@@ -1165,7 +1165,7 @@ proc recv*(socket: Socket, size: int, timeout = -1,
   ## This function will throw an OSError exception when an error occurs.
   ##
   ## A timeout may be specified in milliseconds, if enough data is not received
-  ## within the time specified an ETimeout exception will be raised.
+  ## within the time specified a TimeoutError exception will be raised.
   ##
   ##
   ## **Warning**: Only the ``SafeDisconn`` flag is currently supported.
@@ -1206,7 +1206,7 @@ proc readLine*(socket: Socket, line: var TaintedString, timeout = -1,
   ## An OSError exception will be raised in the case of a socket error.
   ##
   ## A timeout can be specified in milliseconds, if data is not received within
-  ## the specified time an ETimeout exception will be raised.
+  ## the specified time a TimeoutError exception will be raised.
   ##
   ## The ``maxLength`` parameter determines the maximum amount of characters
   ## that can be read. The result is truncated after that.
@@ -1261,7 +1261,7 @@ proc recvLine*(socket: Socket, timeout = -1,
   ## An OSError exception will be raised in the case of a socket error.
   ##
   ## A timeout can be specified in milliseconds, if data is not received within
-  ## the specified time an ETimeout exception will be raised.
+  ## the specified time a TimeoutError exception will be raised.
   ##
   ## The ``maxLength`` parameter determines the maximum amount of characters
   ## that can be read. The result is truncated after that.
@@ -1303,7 +1303,7 @@ proc skip*(socket: Socket, size: int, timeout = -1) =
   ## Skips ``size`` amount of bytes.
   ##
   ## An optional timeout can be specified in milliseconds, if skipping the
-  ## bytes takes longer than specified an ETimeout exception will be raised.
+  ## bytes takes longer than specified a TimeoutError exception will be raised.
   ##
   ## Returns the number of skipped bytes.
   var waited = 0.0
diff --git a/lib/pure/times.nim b/lib/pure/times.nim
index dc79877fd..9312b7ba8 100644
--- a/lib/pure/times.nim
+++ b/lib/pure/times.nim
@@ -282,6 +282,12 @@ type
     dFri = "Friday"
     dSat = "Saturday"
     dSun = "Sunday"
+  
+  DateTimeLocale* = object
+    MMM*: array[mJan..mDec, string]
+    MMMM*: array[mJan..mDec, string]
+    ddd*: array[dMon..dSun, string]
+    dddd*: array[dMon..dSun, string]
 
   MonthdayRange* = range[1..31]
   HourRange* = range[0..23]
@@ -421,6 +427,13 @@ const unitWeights: array[FixedTimeUnit, int64] = [
   7 * secondsInDay * 1e9.int64,
 ]
 
+const DefaultLocale* = DateTimeLocale(
+  MMM: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
+  MMMM: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
+  ddd: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
+  dddd: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
+)
+
 proc convert*[T: SomeInteger](unitFrom, unitTo: FixedTimeUnit, quantity: T): T
     {.inline.} =
   ## Convert a quantity of some duration unit to another duration unit.
@@ -1894,7 +1907,7 @@ proc initTimeFormat*(format: string): TimeFormat =
     of tkPattern:
       result.patterns.add(stringToPattern(token).byte)
 
-proc formatPattern(dt: DateTime, pattern: FormatPattern, result: var string) =
+proc formatPattern(dt: DateTime, pattern: FormatPattern, result: var string, loc: DateTimeLocale) =
   template yearOfEra(dt: DateTime): int =
     if dt.year <= 0: abs(dt.year) + 1 else: dt.year
 
@@ -1904,9 +1917,9 @@ proc formatPattern(dt: DateTime, pattern: FormatPattern, result: var string) =
   of dd:
     result.add dt.monthday.intToStr(2)
   of ddd:
-    result.add ($dt.weekday)[0..2]
+    result.add loc.ddd[dt.weekday]
   of dddd:
-    result.add $dt.weekday
+    result.add loc.dddd[dt.weekday]
   of h:
     result.add(
       if dt.hour == 0:   "12"
@@ -1932,9 +1945,9 @@ proc formatPattern(dt: DateTime, pattern: FormatPattern, result: var string) =
   of MM:
     result.add ord(dt.month).intToStr(2)
   of MMM:
-    result.add ($dt.month)[0..2]
+    result.add loc.MMM[dt.month]
   of MMMM:
-    result.add $dt.month
+    result.add loc.MMMM[dt.month]
   of s:
     result.add $dt.second
   of ss:
@@ -2003,7 +2016,7 @@ proc formatPattern(dt: DateTime, pattern: FormatPattern, result: var string) =
   of Lit: assert false # Can't happen
 
 proc parsePattern(input: string, pattern: FormatPattern, i: var int,
-                  parsed: var ParsedTime): bool =
+                  parsed: var ParsedTime, loc: DateTimeLocale): bool =
   template takeInt(allowedWidth: Slice[int], allowSign = false): int =
     var sv: int
     var pd = parseInt(input, sv, i, allowedWidth.b, allowSign)
@@ -2027,27 +2040,19 @@ proc parsePattern(input: string, pattern: FormatPattern, i: var int,
     parsed.monthday = some(monthday)
     result = monthday in MonthdayRange
   of ddd:
-    result = input.substr(i, i+2).toLowerAscii() in [
-      "sun", "mon", "tue", "wed", "thu", "fri", "sat"]
-    if result:
-      i.inc 3
+    result = false
+    for v in loc.ddd:
+      if input.substr(i, i+v.len-1).cmpIgnoreCase(v) == 0:
+        result = true
+        i.inc v.len
+        break
   of dddd:
-    if input.substr(i, i+5).cmpIgnoreCase("sunday") == 0:
-      i.inc 6
-    elif input.substr(i, i+5).cmpIgnoreCase("monday") == 0:
-      i.inc 6
-    elif input.substr(i, i+6).cmpIgnoreCase("tuesday") == 0:
-      i.inc 7
-    elif input.substr(i, i+8).cmpIgnoreCase("wednesday") == 0:
-      i.inc 9
-    elif input.substr(i, i+7).cmpIgnoreCase("thursday") == 0:
-      i.inc 8
-    elif input.substr(i, i+5).cmpIgnoreCase("friday") == 0:
-      i.inc 6
-    elif input.substr(i, i+7).cmpIgnoreCase("saturday") == 0:
-      i.inc 8
-    else:
-      result = false
+    result = false
+    for v in loc.dddd:
+      if input.substr(i, i+v.len-1).cmpIgnoreCase(v) == 0:
+        result = true
+        i.inc v.len
+        break
   of h, H:
     parsed.hour = takeInt(1..2)
     result = parsed.hour in HourRange
@@ -2069,62 +2074,21 @@ proc parsePattern(input: string, pattern: FormatPattern, i: var int,
     result = month in 1..12
     parsed.month = some(month)
   of MMM:
-    case input.substr(i, i+2).toLowerAscii()
-    of "jan": parsed.month = some(1)
-    of "feb": parsed.month = some(2)
-    of "mar": parsed.month = some(3)
-    of "apr": parsed.month = some(4)
-    of "may": parsed.month = some(5)
-    of "jun": parsed.month = some(6)
-    of "jul": parsed.month = some(7)
-    of "aug": parsed.month = some(8)
-    of "sep": parsed.month = some(9)
-    of "oct": parsed.month = some(10)
-    of "nov": parsed.month = some(11)
-    of "dec": parsed.month = some(12)
-    else:
-      result = false
-    if result:
-      i.inc 3
+    result = false
+    for n,v in loc.MMM:
+      if input.substr(i, i+v.len-1).cmpIgnoreCase(v) == 0:
+        result = true
+        i.inc v.len
+        parsed.month = some(n.int)
+        break
   of MMMM:
-    if input.substr(i, i+6).cmpIgnoreCase("january") == 0:
-      parsed.month = some(1)
-      i.inc 7
-    elif input.substr(i, i+7).cmpIgnoreCase("february") == 0:
-      parsed.month = some(2)
-      i.inc 8
-    elif input.substr(i, i+4).cmpIgnoreCase("march") == 0:
-      parsed.month = some(3)
-      i.inc 5
-    elif input.substr(i, i+4).cmpIgnoreCase("april") == 0:
-      parsed.month = some(4)
-      i.inc 5
-    elif input.substr(i, i+2).cmpIgnoreCase("may") == 0:
-      parsed.month = some(5)
-      i.inc 3
-    elif input.substr(i, i+3).cmpIgnoreCase("june") == 0:
-      parsed.month = some(6)
-      i.inc 4
-    elif input.substr(i, i+3).cmpIgnoreCase("july") == 0:
-      parsed.month = some(7)
-      i.inc 4
-    elif input.substr(i, i+5).cmpIgnoreCase("august") == 0:
-      parsed.month = some(8)
-      i.inc 6
-    elif input.substr(i, i+8).cmpIgnoreCase("september") == 0:
-      parsed.month = some(9)
-      i.inc 9
-    elif input.substr(i, i+6).cmpIgnoreCase("october") == 0:
-      parsed.month = some(10)
-      i.inc 7
-    elif input.substr(i, i+7).cmpIgnoreCase("november") == 0:
-      parsed.month = some(11)
-      i.inc 8
-    elif input.substr(i, i+7).cmpIgnoreCase("december") == 0:
-      parsed.month = some(12)
-      i.inc 8
-    else:
-      result = false
+    result = false
+    for n,v in loc.MMMM:
+      if input.substr(i, i+v.len-1).cmpIgnoreCase(v) == 0:
+        result = true
+        i.inc v.len
+        parsed.month = some(n.int)
+        break
   of s:
     parsed.second = takeInt(1..2)
   of ss:
@@ -2294,7 +2258,7 @@ proc toDateTime(p: ParsedTime, zone: Timezone, f: TimeFormat,
     result.utcOffset = p.utcOffset.get()
     result = result.toTime.inZone(zone)
 
-proc format*(dt: DateTime, f: TimeFormat): string {.raises: [].} =
+proc format*(dt: DateTime, f: TimeFormat, loc: DateTimeLocale = DefaultLocale): string {.raises: [].} =
   ## Format ``dt`` using the format specified by ``f``.
   runnableExamples:
     let f = initTimeFormat("yyyy-MM-dd")
@@ -2311,10 +2275,10 @@ proc format*(dt: DateTime, f: TimeFormat): string {.raises: [].} =
         result.add f.patterns[idx].char
       idx.inc
     else:
-      formatPattern(dt, f.patterns[idx].FormatPattern, result = result)
+      formatPattern(dt, f.patterns[idx].FormatPattern, result = result, loc = loc)
       idx.inc
 
-proc format*(dt: DateTime, f: string): string
+proc format*(dt: DateTime, f: string, loc: DateTimeLocale = DefaultLocale): string
     {.raises: [TimeFormatParseError].} =
   ## Shorthand for constructing a ``TimeFormat`` and using it to format ``dt``.
   ##
@@ -2324,7 +2288,7 @@ proc format*(dt: DateTime, f: string): string
     let dt = initDateTime(01, mJan, 2000, 00, 00, 00, utc())
     doAssert "2000-01-01" == format(dt, "yyyy-MM-dd")
   let dtFormat = initTimeFormat(f)
-  result = dt.format(dtFormat)
+  result = dt.format(dtFormat, loc)
 
 proc format*(dt: DateTime, f: static[string]): string {.raises: [].} =
   ## Overload that validates ``format`` at compile time.
@@ -2358,12 +2322,14 @@ template formatValue*(result: var string; value: Time, specifier: string) =
   ## adapter for strformat. Not intended to be called directly.
   result.add format(value, specifier)
 
-proc parse*(input: string, f: TimeFormat, zone: Timezone = local()): DateTime
+proc parse*(input: string, f: TimeFormat, zone: Timezone = local(), loc: DateTimeLocale = DefaultLocale): DateTime
     {.raises: [TimeParseError, Defect].} =
   ## Parses ``input`` as a ``DateTime`` using the format specified by ``f``.
   ## If no UTC offset was parsed, then ``input`` is assumed to be specified in
   ## the ``zone`` timezone. If a UTC offset was parsed, the result will be
   ## converted to the ``zone`` timezone.
+  ##
+  ## Month and day names from the passed in ``loc`` are used.
   runnableExamples:
     let f = initTimeFormat("yyyy-MM-dd")
     let dt = initDateTime(01, mJan, 2000, 00, 00, 00, utc())
@@ -2385,7 +2351,7 @@ proc parse*(input: string, f: TimeFormat, zone: Timezone = local()): DateTime
         inpIdx.inc
         patIdx.inc
     else:
-      if not parsePattern(input, pattern, inpIdx, parsed):
+      if not parsePattern(input, pattern, inpIdx, parsed, loc):
         raiseParseException(f, input, "Failed on pattern '" & $pattern & "'")
       patIdx.inc
 
@@ -2399,7 +2365,7 @@ proc parse*(input: string, f: TimeFormat, zone: Timezone = local()): DateTime
 
   result = toDateTime(parsed, zone, f, input)
 
-proc parse*(input, f: string, tz: Timezone = local()): DateTime
+proc parse*(input, f: string, tz: Timezone = local(), loc: DateTimeLocale = DefaultLocale): DateTime
     {.raises: [TimeParseError, TimeFormatParseError, Defect].} =
   ## Shorthand for constructing a ``TimeFormat`` and using it to parse
   ## ``input`` as a ``DateTime``.
@@ -2410,13 +2376,13 @@ proc parse*(input, f: string, tz: Timezone = local()): DateTime
     let dt = initDateTime(01, mJan, 2000, 00, 00, 00, utc())
     doAssert dt == parse("2000-01-01", "yyyy-MM-dd", utc())
   let dtFormat = initTimeFormat(f)
-  result = input.parse(dtFormat, tz)
+  result = input.parse(dtFormat, tz, loc = loc)
 
-proc parse*(input: string, f: static[string], zone: Timezone = local()):
+proc parse*(input: string, f: static[string], zone: Timezone = local(), loc: DateTimeLocale = DefaultLocale):
     DateTime {.raises: [TimeParseError, Defect].} =
   ## Overload that validates ``f`` at compile time.
   const f2 = initTimeFormat(f)
-  result = input.parse(f2, zone)
+  result = input.parse(f2, zone, loc = loc)
 
 proc parseTime*(input, f: string, zone: Timezone): Time
     {.raises: [TimeParseError, TimeFormatParseError, Defect].} =
diff --git a/testament/categories.nim b/testament/categories.nim
index a2a0c5d5b..e0f57df8d 100644
--- a/testament/categories.nim
+++ b/testament/categories.nim
@@ -551,7 +551,8 @@ proc testNimblePackages(r: var TResults, cat: Category) =
 
 # ----------------------------------------------------------------------------
 
-const AdditionalCategories = ["debugger", "examples", "lib", "megatest"]
+const AdditionalCategories = ["debugger", "examples", "lib"]
+const MegaTestCat = "megatest"
 
 proc `&.?`(a, b: string): string =
   # candidate for the stdlib?
@@ -679,22 +680,9 @@ proc runJoinedTest(r: var TResults, cat: Category, testsDir: string) =
 
 # ---------------------------------------------------------------------------
 
-proc loadSkipFrom(name: string): seq[string] =
-  # One skip per line, comments start with #
-  # used by `nlvm` (at least)
-  try:
-    for line in lines(name):
-      let sline = line.strip()
-      if sline.len > 0 and not sline.startsWith("#"):
-        result.add sline
-  except:
-    echo "Could not load " & name & ", ignoring"
-
 proc processCategory(r: var TResults, cat: Category,
-                     options, testsDir, skipFrom: string,
+                     options, testsDir: string,
                      runJoinableTests: bool) =
-  let skips = loadSkipFrom(skipFrom)
-
   case cat.string.normalize
   of "rodfiles":
     when false:
@@ -750,9 +738,6 @@ proc processCategory(r: var TResults, cat: Category,
 
     for i, name in files:
       var test = makeTest(name, options, cat)
-      if skips.anyIt(it in name):
-        test.spec.err = reDisabled
-
       if runJoinableTests or not isJoinableSpec(test.spec) or cat.string in specialCategories:
         discard "run the test"
       else:
diff --git a/testament/specs.nim b/testament/specs.nim
index 96b02746b..11286ceab 100644
--- a/testament/specs.nim
+++ b/testament/specs.nim
@@ -7,13 +7,15 @@
 #    distribution, for details about the copyright.
 #
 
-import parseutils, strutils, os, osproc, streams, parsecfg
+import sequtils, parseutils, strutils, os, osproc, streams, parsecfg
 
 var compilerPrefix* = findExe("nim")
 
 let isTravis* = existsEnv("TRAVIS")
 let isAppVeyor* = existsEnv("APPVEYOR")
 
+var skips*: seq[string]
+
 type
   TTestAction* = enum
     actionRun = "run"
@@ -245,3 +247,6 @@ proc parseSpec*(filename: string): TSpec =
     of cfgEof:
       break
   close(p)
+
+  if skips.anyIt(it in result.file):
+    result.err = reDisabled
diff --git a/testament/tester.nim b/testament/tester.nim
index b73190eba..f172a670d 100644
--- a/testament/tester.nim
+++ b/testament/tester.nim
@@ -44,6 +44,7 @@ Options:
   --directory:dir           Change to directory dir before reading the tests or doing anything else.
   --colors:on|off           Turn messagescoloring on|off.
   --backendLogging:on|off   Disable or enable backend logging. By default turned on.
+  --megatest:on|off         Enable or disable megatest. Default is on.
   --skipFrom:file           Read tests to skip from `file` - one test per line, # comments ignored
 """ % resultsFile
 
@@ -380,6 +381,15 @@ proc getTestSpecTarget(): TTarget =
   else:
     return targetC
 
+proc checkDisabled(r: var TResults, test: TTest): bool =
+  if test.spec.err in {reDisabled, reJoined}:
+    # targetC is a lie, but parameter is required
+    r.addResult(test, targetC, "", "", test.spec.err)
+    inc(r.skipped)
+    inc(r.total)
+    return
+  true
+
 proc testSpec(r: var TResults, test: TTest, targets: set[TTarget] = {}) =
   var expected = test.spec
   if expected.parseErrors.len > 0:
@@ -387,12 +397,8 @@ proc testSpec(r: var TResults, test: TTest, targets: set[TTarget] = {}) =
     r.addResult(test, targetC, "", expected.parseErrors, reInvalidSpec)
     inc(r.total)
     return
-  if expected.err in {reDisabled, reJoined}:
-    # targetC is a lie, but parameter is required
-    r.addResult(test, targetC, "", "", expected.err)
-    inc(r.skipped)
-    inc(r.total)
-    return
+  if not checkDisabled(r, test): return
+
   expected.targets.incl targets
   # still no target specified at all
   if expected.targets == {}:
@@ -472,6 +478,8 @@ proc testSpec(r: var TResults, test: TTest, targets: set[TTarget] = {}) =
 
 proc testC(r: var TResults, test: TTest, action: TTestAction) =
   # runs C code. Doesn't support any specs, just goes by exit code.
+  if not checkDisabled(r, test): return
+
   let tname = test.name.addFileExt(".c")
   inc(r.total)
   maybeStyledEcho "Processing ", fgCyan, extractFilename(tname)
@@ -486,6 +494,8 @@ proc testC(r: var TResults, test: TTest, action: TTestAction) =
 
 proc testExec(r: var TResults, test: TTest) =
   # runs executable or script, just goes by exit code
+  if not checkDisabled(r, test): return
+
   inc(r.total)
   let (outp, errC) = execCmdEx(test.options.strip())
   var given: TSpec
@@ -534,6 +544,19 @@ else:
 
 include categories
 
+proc loadSkipFrom(name: string): seq[string] =
+  if name.len() == 0: return
+
+  # One skip per line, comments start with #
+  # used by `nlvm` (at least)
+  try:
+    for line in lines(name):
+      let sline = line.strip()
+      if sline.len > 0 and not sline.startsWith("#"):
+        result.add sline
+  except:
+    echo "Could not load " & name & ", ignoring"
+
 proc main() =
   os.putenv "NIMTEST_COLOR", "never"
   os.putenv "NIMTEST_OUTPUT_LVL", "PRINT_FAILURES"
@@ -544,6 +567,7 @@ proc main() =
   var targetsStr = ""
   var isMainProcess = true
   var skipFrom = ""
+  var useMegatest = true
 
   var p = initOptParser()
   p.next()
@@ -569,6 +593,14 @@ proc main() =
         quit Usage
     of "simulate":
       simulate = true
+    of "megatest":
+      case p.val.string:
+      of "on":
+        useMegatest = true
+      of "off":
+        useMegatest = false
+      else:
+        quit Usage
     of "backendlogging":
       case p.val.string:
       of "on":
@@ -608,32 +640,37 @@ proc main() =
       if kind == pcDir and cat notin ["testdata", "nimcache"]:
         cats.add cat
     cats.add AdditionalCategories
+    if useMegatest: cats.add MegaTestCat
 
     var cmds: seq[string]
     for cat in cats:
-      cmds.add(myself & " pcat " & quoteShell(cat) & rest)
+      let runtype = if useMegatest: " pcat " else: " cat "
+      cmds.add(myself & runtype & quoteShell(cat) & rest)
 
     proc progressStatus(idx: int) =
       echo "progress[all]: i: " & $idx & " / " & $cats.len & " cat: " & cats[idx]
 
     if simulate:
+      skips = loadSkipFrom(skipFrom)
       for i, cati in cats:
         progressStatus(i)
-        processCategory(r, Category(cati), p.cmdLineRest.string, testsDir, skipFrom, runJoinableTests = false)
+        processCategory(r, Category(cati), p.cmdLineRest.string, testsDir, runJoinableTests = false)
     else:
       quit osproc.execProcesses(cmds, {poEchoCmd, poStdErrToStdOut, poUsePath, poParentStreams}, beforeRunEvent = progressStatus)
   of "c", "cat", "category":
+    skips = loadSkipFrom(skipFrom)
     var cat = Category(p.key)
     p.next
-    processCategory(r, cat, p.cmdLineRest.string, testsDir, skipFrom, runJoinableTests = true)
+    processCategory(r, cat, p.cmdLineRest.string, testsDir, runJoinableTests = true)
   of "pcat":
+    skips = loadSkipFrom(skipFrom)
     # 'pcat' is used for running a category in parallel. Currently the only
     # difference is that we don't want to run joinable tests here as they
     # are covered by the 'megatest' category.
     isMainProcess = false
     var cat = Category(p.key)
     p.next
-    processCategory(r, cat, p.cmdLineRest.string, testsDir, skipFrom, runJoinableTests = false)
+    processCategory(r, cat, p.cmdLineRest.string, testsDir, runJoinableTests = false)
   of "r", "run":
     # at least one directory is required in the path, to use as a category name
     let pathParts = split(p.key.string, {DirSep, AltSep})
diff --git a/tests/errmsgs/tuncheckedarrayvar.nim b/tests/errmsgs/tuncheckedarrayvar.nim
new file mode 100644
index 000000000..17965914a
--- /dev/null
+++ b/tests/errmsgs/tuncheckedarrayvar.nim
@@ -0,0 +1,9 @@
+discard """
+errormsg: '''
+invalid type: 'UncheckedArray[uint8]' for var
+'''
+"""
+
+var
+  rawMem = alloc0(20)
+  byteUA = cast[UncheckedArray[uint8]](rawMem)
diff --git a/tests/macros/tmacros_issues.nim b/tests/macros/tmacros_issues.nim
index 657f30fc4..d32b6a2a2 100644
--- a/tests/macros/tmacros_issues.nim
+++ b/tests/macros/tmacros_issues.nim
@@ -4,7 +4,7 @@ IntLit 5
 proc (x: int): string => typeDesc[proc[string, int]]
 proc (x: int): void => typeDesc[proc[void, int]]
 proc (x: int) => typeDesc[proc[void, int]]
-x => UncheckedArray[int]
+x => seq[int]
 a
 s
 d
@@ -111,7 +111,7 @@ block t2211:
   showType(proc(x:int): void)
   showType(proc(x:int))
 
-  var x: UncheckedArray[int]
+  var x: seq[int]
   showType(x)
 
 
diff --git a/tests/stdlib/ttimes.nim b/tests/stdlib/ttimes.nim
index b29f5090b..ef6712171 100644
--- a/tests/stdlib/ttimes.nim
+++ b/tests/stdlib/ttimes.nim
@@ -452,6 +452,23 @@ suite "ttimes":
       doAssert dt.format("zz") == tz[2]
       doAssert dt.format("zzz") == tz[3]
 
+  test "format locale":
+    let loc = DateTimeLocale(
+      MMM: ["Fir","Sec","Thi","Fou","Fif","Six","Sev","Eig","Nin","Ten","Ele","Twe"],
+      MMMM: ["Firsty", "Secondy", "Thirdy", "Fourthy", "Fifthy", "Sixthy", "Seventhy", "Eighthy", "Ninthy", "Tenthy", "Eleventhy", "Twelfthy"],
+      ddd: ["Red", "Ora.", "Yel.", "Gre.", "Blu.", "Vio.", "Whi."],
+      dddd: ["Red", "Orange", "Yellow", "Green", "Blue", "Violet", "White"],
+    )
+    var dt = initDateTime(5, mJan, 2010, 17, 01, 02, utc())
+    check dt.format("d", loc) == "5"
+    check dt.format("dd", loc) == "05"
+    check dt.format("ddd", loc) == "Ora."
+    check dt.format("dddd", loc) == "Orange"
+    check dt.format("M", loc) == "1"
+    check dt.format("MM", loc) == "01"
+    check dt.format("MMM", loc) == "Fir"
+    check dt.format("MMMM", loc) == "Firsty"
+
   test "parse":
     check $parse("20180101", "yyyyMMdd", utc()) == "2018-01-01T00:00:00Z"
     parseTestExcp("+120180101", "yyyyMMdd")
@@ -473,6 +490,16 @@ suite "ttimes":
 
     parseTestExcp("2000 A", "yyyy g")
 
+  test "parse locale":
+    let loc = DateTimeLocale(
+      MMM: ["Fir","Sec","Thi","Fou","Fif","Six","Sev","Eig","Nin","Ten","Ele","Twe"],
+      MMMM: ["Firsty", "Secondy", "Thirdy", "Fourthy", "Fifthy", "Sixthy", "Seventhy", "Eighthy", "Ninthy", "Tenthy", "Eleventhy", "Twelfthy"],
+      ddd: ["Red", "Ora.", "Yel.", "Gre.", "Blu.", "Vio.", "Whi."],
+      dddd: ["Red", "Orange", "Yellow", "Green", "Blue", "Violet", "White"],
+    )
+    check $parse("02 Fir 2019", "dd MMM yyyy", utc(), loc) == "2019-01-02T00:00:00Z"
+    check $parse("Fourthy 6, 2017", "MMMM d, yyyy", utc(), loc) == "2017-04-06T00:00:00Z"
+
   test "countLeapYears":
     # 1920, 2004 and 2020 are leap years, and should be counted starting at the following year
     check countLeapYears(1920) + 1 == countLeapYears(1921)