diff --git a/std/datetime/README.md b/std/datetime/README.md
index e9142f48ad..ac505ad160 100644
--- a/std/datetime/README.md
+++ b/std/datetime/README.md
@@ -4,22 +4,63 @@ Simple helper to help parse date strings into `Date`, with additional functions.
 
 ## Usage
 
-### parseDate / parseDateTime
+The following symbols are supported:
 
-- `parseDate()` - Take an input string and a format to parse the date. Supported
-  formats are exported in `DateFormat`.
-- `parseDateTime()` - Take an input string and a format to parse the dateTime.
-  Supported formats are exported in `DateTimeFormat`.
+- `yyyy` - numeric year
+- `yy` - 2-digit year
+- `M` - numeric month
+- `MM` - 2-digit month
+- `d` - numeric day
+- `dd` - 2-digit day
+
+- `h` - numeric hour
+- `hh` - 2-digit hour
+- `m` - numeric minute
+- `mm` - 2-digit minute
+- `s` - numeric second
+- `ss` - 2-digit second
+- `S` - 1-digit fractionalSecond
+- `SS` - 2-digit fractionalSecond
+- `SSS` - 3-digit fractionalSecond
+
+- `a` - dayPeriod, either `AM` or `PM`
+
+- `'foo'` - quoted literal
+- `./-` - unquoted literal
+
+### parse
+
+Takes an input `string` and a `formatString` to parse to a `date`.
 
 ```ts
-import { parseDate, parseDateTime } from 'https://deno.land/std/datetime/mod.ts'
+import { parse } from 'https://deno.land/std/datetime/mod.ts'
 
-parseDate("20-01-2019", "dd-mm-yyyy") // output : new Date(2019, 0, 20)
-parseDate("2019-01-20", "yyyy-mm-dd") // output : new Date(2019, 0, 20)
+parse("20-01-2019", "dd-MM-yyyy") // output : new Date(2019, 0, 20)
+parse("2019-01-20", "yyyy-MM-dd") // output : new Date(2019, 0, 20)
+parse("2019-01-20", "dd.MM.yyyy") // output : new Date(2019, 0, 20)
+parse("01-20-2019 16:34", "MM-dd-yyyy hh:mm") // output : new Date(2019, 0, 20, 16, 34)
+parse("01-20-2019 04:34 PM", "MM-dd-yyyy hh:mm a") // output : new Date(2019, 0, 20, 16, 34)
+parse("16:34 01-20-2019", "hh:mm MM-dd-yyyy") // output : new Date(2019, 0, 20, 16, 34)
+parse("01-20-2019 16:34:23.123", "MM-dd-yyyy hh:mm:ss.SSS") // output : new Date(2019, 0, 20, 16, 34, 23, 123)
 ...
+```
+
+### format
+
+Takes an input `date` and a `formatString` to format to a `string`.
+
+```ts
+import { format } from 'https://deno.land/std/datetime/mod.ts'
+
+format(new Date(2019, 0, 20), "dd-MM-yyyy") // output : "20-01-2019"
+format(new Date(2019, 0, 20), "yyyy-MM-dd") // output : "2019-01-20"
+format(new Date(2019, 0, 20), "dd.MM.yyyy") // output : "2019-01-20"
+format(new Date(2019, 0, 20, 16, 34), "MM-dd-yyyy hh:mm") // output : "01-20-2019 16:34"
+format(new Date(2019, 0, 20, 16, 34), "MM-dd-yyyy hh:mm a") // output : "01-20-2019 04:34 PM"
+format(new Date(2019, 0, 20, 16, 34), "hh:mm MM-dd-yyyy") // output : "16:34 01-20-2019"
+format(new Date(2019, 0, 20, 16, 34, 23, 123), "MM-dd-yyyy hh:mm:ss.SSS") // output : "01-20-2019 16:34:23.123"
+format(new Date(2019, 0, 20), "'today:' yyyy-MM-dd") // output : "today: 2019-01-20"
 
-parseDateTime("01-20-2019 16:34", "mm-dd-yyyy hh:mm") // output : new Date(2019, 0, 20, 16, 34)
-parseDateTime("16:34 01-20-2019", "hh:mm mm-dd-yyyy") // output : new Date(2019, 0, 20, 16, 34)
 ...
 ```
 
diff --git a/std/datetime/formatter.ts b/std/datetime/formatter.ts
new file mode 100644
index 0000000000..0e872cb401
--- /dev/null
+++ b/std/datetime/formatter.ts
@@ -0,0 +1,534 @@
+import {
+  CallbackResult,
+  Rule,
+  TestFunction,
+  TestResult,
+  Tokenizer,
+} from "./tokenizer.ts";
+
+function digits(value: string | number, count = 2): string {
+  return String(value).padStart(count, "0");
+}
+
+// as declared as in namespace Intl
+type DateTimeFormatPartTypes =
+  | "day"
+  | "dayPeriod"
+  // | "era"
+  | "hour"
+  | "literal"
+  | "minute"
+  | "month"
+  | "second"
+  | "timeZoneName"
+  // | "weekday"
+  | "year"
+  | "fractionalSecond";
+
+interface DateTimeFormatPart {
+  type: DateTimeFormatPartTypes;
+  value: string;
+}
+
+type TimeZone = "UTC";
+
+interface Options {
+  timeZone?: TimeZone;
+}
+
+function createLiteralTestFunction(value: string): TestFunction {
+  return (string: string): TestResult => {
+    return string.startsWith(value)
+      ? { value, length: value.length }
+      : undefined;
+  };
+}
+
+function createMatchTestFunction(match: RegExp): TestFunction {
+  return (string: string): TestResult => {
+    const result = match.exec(string);
+    if (result) return { value: result, length: result[0].length };
+  };
+}
+
+// according to unicode symbols (http://userguide.icu-project.org/formatparse/datetime)
+const defaultRules = [
+  {
+    test: createLiteralTestFunction("yyyy"),
+    fn: (): CallbackResult => ({ type: "year", value: "numeric" }),
+  },
+  {
+    test: createLiteralTestFunction("yy"),
+    fn: (): CallbackResult => ({ type: "year", value: "2-digit" }),
+  },
+
+  {
+    test: createLiteralTestFunction("MM"),
+    fn: (): CallbackResult => ({ type: "month", value: "2-digit" }),
+  },
+  {
+    test: createLiteralTestFunction("M"),
+    fn: (): CallbackResult => ({ type: "month", value: "numeric" }),
+  },
+  {
+    test: createLiteralTestFunction("dd"),
+    fn: (): CallbackResult => ({ type: "day", value: "2-digit" }),
+  },
+  {
+    test: createLiteralTestFunction("d"),
+    fn: (): CallbackResult => ({ type: "day", value: "numeric" }),
+  },
+
+  {
+    test: createLiteralTestFunction("hh"),
+    fn: (): CallbackResult => ({ type: "hour", value: "2-digit" }),
+  },
+  {
+    test: createLiteralTestFunction("h"),
+    fn: (): CallbackResult => ({ type: "hour", value: "numeric" }),
+  },
+  {
+    test: createLiteralTestFunction("mm"),
+    fn: (): CallbackResult => ({ type: "minute", value: "2-digit" }),
+  },
+  {
+    test: createLiteralTestFunction("m"),
+    fn: (): CallbackResult => ({ type: "minute", value: "numeric" }),
+  },
+  {
+    test: createLiteralTestFunction("ss"),
+    fn: (): CallbackResult => ({ type: "second", value: "2-digit" }),
+  },
+  {
+    test: createLiteralTestFunction("s"),
+    fn: (): CallbackResult => ({ type: "second", value: "numeric" }),
+  },
+  {
+    test: createLiteralTestFunction("SSS"),
+    fn: (): CallbackResult => ({ type: "fractionalSecond", value: 3 }),
+  },
+  {
+    test: createLiteralTestFunction("SS"),
+    fn: (): CallbackResult => ({ type: "fractionalSecond", value: 2 }),
+  },
+  {
+    test: createLiteralTestFunction("S"),
+    fn: (): CallbackResult => ({ type: "fractionalSecond", value: 1 }),
+  },
+
+  {
+    test: createLiteralTestFunction("a"),
+    fn: (value: unknown): CallbackResult => ({
+      type: "dayPeriod",
+      value: value as string,
+    }),
+  },
+
+  // quoted literal
+  {
+    test: createMatchTestFunction(/^(')(?<value>\\.|[^\']*)\1/),
+    fn: (match: unknown): CallbackResult => ({
+      type: "literal",
+      value: (match as RegExpExecArray).groups!.value as string,
+    }),
+  },
+  // literal
+  {
+    test: createMatchTestFunction(/^.+?\s*/),
+    fn: (match: unknown): CallbackResult => ({
+      type: "literal",
+      value: (match as RegExpExecArray)[0],
+    }),
+  },
+];
+
+type FormatPart = { type: DateTimeFormatPartTypes; value: string | number };
+type Format = FormatPart[];
+
+export class DateTimeFormatter {
+  #format: Format;
+
+  constructor(formatString: string, rules: Rule[] = defaultRules) {
+    const tokenizer = new Tokenizer(rules);
+    this.#format = tokenizer.tokenize(formatString, ({ type, value }) => ({
+      type,
+      value,
+    })) as Format;
+  }
+
+  format(date: Date, options: Options = {}): string {
+    let string = "";
+
+    const utc = options.timeZone === "UTC";
+    const hour12 = this.#format.find(
+      (token: FormatPart) => token.type === "dayPeriod",
+    );
+
+    for (const token of this.#format) {
+      const type = token.type;
+
+      switch (type) {
+        case "year": {
+          const value = utc ? date.getUTCFullYear() : date.getFullYear();
+          switch (token.value) {
+            case "numeric": {
+              string += value;
+              break;
+            }
+            case "2-digit": {
+              string += digits(value, 2).slice(-2);
+              break;
+            }
+            default:
+              throw Error(
+                `FormatterError: value "${token.value}" is not supported`,
+              );
+          }
+          break;
+        }
+        case "month": {
+          const value = (utc ? date.getUTCMonth() : date.getMonth()) + 1;
+          switch (token.value) {
+            case "numeric": {
+              string += value;
+              break;
+            }
+            case "2-digit": {
+              string += digits(value, 2);
+              break;
+            }
+            default:
+              throw Error(
+                `FormatterError: value "${token.value}" is not supported`,
+              );
+          }
+          break;
+        }
+        case "day": {
+          const value = utc ? date.getUTCDate() : date.getDate();
+          switch (token.value) {
+            case "numeric": {
+              string += value;
+              break;
+            }
+            case "2-digit": {
+              string += digits(value, 2);
+              break;
+            }
+            default:
+              throw Error(
+                `FormatterError: value "${token.value}" is not supported`,
+              );
+          }
+          break;
+        }
+        case "hour": {
+          let value = utc ? date.getUTCHours() : date.getHours();
+          value -= hour12 && date.getHours() > 12 ? 12 : 0;
+          switch (token.value) {
+            case "numeric": {
+              string += value;
+              break;
+            }
+            case "2-digit": {
+              string += digits(value, 2);
+              break;
+            }
+            default:
+              throw Error(
+                `FormatterError: value "${token.value}" is not supported`,
+              );
+          }
+          break;
+        }
+        case "minute": {
+          const value = utc ? date.getUTCMinutes() : date.getMinutes();
+          switch (token.value) {
+            case "numeric": {
+              string += value;
+              break;
+            }
+            case "2-digit": {
+              string += digits(value, 2);
+              break;
+            }
+            default:
+              throw Error(
+                `FormatterError: value "${token.value}" is not supported`,
+              );
+          }
+          break;
+        }
+        case "second": {
+          const value = utc ? date.getUTCSeconds() : date.getSeconds();
+          switch (token.value) {
+            case "numeric": {
+              string += value;
+              break;
+            }
+            case "2-digit": {
+              string += digits(value, 2);
+              break;
+            }
+            default:
+              throw Error(
+                `FormatterError: value "${token.value}" is not supported`,
+              );
+          }
+          break;
+        }
+        case "fractionalSecond": {
+          const value = utc
+            ? date.getUTCMilliseconds()
+            : date.getMilliseconds();
+          string += digits(value, Number(token.value));
+          break;
+        }
+        case "timeZoneName": {
+          // string += utc ? "Z" : token.value
+          // break
+        }
+        case "dayPeriod": {
+          string += hour12 ? (date.getHours() >= 12 ? "PM" : "AM") : "";
+          break;
+        }
+        case "literal": {
+          string += token.value;
+          break;
+        }
+
+        default:
+          throw Error(`FormatterError: { ${token.type} ${token.value} }`);
+      }
+    }
+
+    return string;
+  }
+
+  parseToParts(string: string): DateTimeFormatPart[] {
+    const parts: DateTimeFormatPart[] = [];
+
+    for (const token of this.#format) {
+      const type = token.type;
+
+      let value = "";
+      switch (token.type) {
+        case "year": {
+          switch (token.value) {
+            case "numeric": {
+              value = /^\d{1,4}/.exec(string)?.[0] as string;
+              break;
+            }
+            case "2-digit": {
+              value = /^\d{1,2}/.exec(string)?.[0] as string;
+              break;
+            }
+          }
+          break;
+        }
+        case "month": {
+          switch (token.value) {
+            case "numeric": {
+              value = /^\d{1,2}/.exec(string)?.[0] as string;
+              break;
+            }
+            case "2-digit": {
+              value = /^\d{2}/.exec(string)?.[0] as string;
+              break;
+            }
+            case "narrow": {
+              value = /^[a-zA-Z]+/.exec(string)?.[0] as string;
+              break;
+            }
+            case "short": {
+              value = /^[a-zA-Z]+/.exec(string)?.[0] as string;
+              break;
+            }
+            case "long": {
+              value = /^[a-zA-Z]+/.exec(string)?.[0] as string;
+              break;
+            }
+            default:
+              throw Error(
+                `ParserError: value "${token.value}" is not supported`,
+              );
+          }
+          break;
+        }
+        case "day": {
+          switch (token.value) {
+            case "numeric": {
+              value = /^\d{1,2}/.exec(string)?.[0] as string;
+              break;
+            }
+            case "2-digit": {
+              value = /^\d{2}/.exec(string)?.[0] as string;
+              break;
+            }
+            default:
+              throw Error(
+                `ParserError: value "${token.value}" is not supported`,
+              );
+          }
+          break;
+        }
+        case "hour": {
+          switch (token.value) {
+            case "numeric": {
+              value = /^\d{1,2}/.exec(string)?.[0] as string;
+              break;
+            }
+            case "2-digit": {
+              value = /^\d{2}/.exec(string)?.[0] as string;
+              break;
+            }
+            default:
+              throw Error(
+                `ParserError: value "${token.value}" is not supported`,
+              );
+          }
+          break;
+        }
+        case "minute": {
+          switch (token.value) {
+            case "numeric": {
+              value = /^\d{1,2}/.exec(string)?.[0] as string;
+              break;
+            }
+            case "2-digit": {
+              value = /^\d{2}/.exec(string)?.[0] as string;
+              break;
+            }
+            default:
+              throw Error(
+                `ParserError: value "${token.value}" is not supported`,
+              );
+          }
+          break;
+        }
+        case "second": {
+          switch (token.value) {
+            case "numeric": {
+              value = /^\d{1,2}/.exec(string)?.[0] as string;
+              break;
+            }
+            case "2-digit": {
+              value = /^\d{2}/.exec(string)?.[0] as string;
+              break;
+            }
+            default:
+              throw Error(
+                `ParserError: value "${token.value}" is not supported`,
+              );
+          }
+          break;
+        }
+        case "fractionalSecond": {
+          value = new RegExp(`^\\d{${token.value}}`).exec(
+            string,
+          )?.[0] as string;
+          break;
+        }
+        case "timeZoneName": {
+          value = token.value as string;
+          break;
+        }
+        case "dayPeriod": {
+          value = /^(A|P)M/.exec(string)?.[0] as string;
+          break;
+        }
+        case "literal": {
+          if (!string.startsWith(token.value as string)) {
+            throw Error(
+              `Literal "${token.value}" not found "${string.slice(0, 25)}"`,
+            );
+          }
+          value = token.value as string;
+          break;
+        }
+
+        default:
+          throw Error(`${token.type} ${token.value}`);
+      }
+
+      if (!value) {
+        throw Error(
+          `value not valid for token { ${type} ${value} } ${
+            string.slice(
+              0,
+              25,
+            )
+          }`,
+        );
+      }
+      parts.push({ type, value });
+      string = string.slice(value.length);
+    }
+
+    if (string.length) {
+      throw Error(
+        `datetime string was not fully parsed! ${string.slice(0, 25)}`,
+      );
+    }
+
+    return parts;
+  }
+
+  partsToDate(parts: DateTimeFormatPart[]): Date {
+    const date = new Date();
+    const utc = parts.find(
+      (part) => part.type === "timeZoneName" && part.value === "UTC",
+    );
+
+    utc ? date.setUTCHours(0, 0, 0, 0) : date.setHours(0, 0, 0, 0);
+    for (const part of parts) {
+      switch (part.type) {
+        case "year": {
+          const value = Number(part.value.padStart(4, "20"));
+          utc ? date.setUTCFullYear(value) : date.setFullYear(value);
+          break;
+        }
+        case "month": {
+          const value = Number(part.value) - 1;
+          utc ? date.setUTCMonth(value) : date.setMonth(value);
+          break;
+        }
+        case "day": {
+          const value = Number(part.value);
+          utc ? date.setUTCDate(value) : date.setDate(value);
+          break;
+        }
+        case "hour": {
+          let value = Number(part.value);
+          const dayPeriod = parts.find(
+            (part: DateTimeFormatPart) => part.type === "dayPeriod",
+          );
+          if (dayPeriod?.value === "PM") value += 12;
+          utc ? date.setUTCHours(value) : date.setHours(value);
+          break;
+        }
+        case "minute": {
+          const value = Number(part.value);
+          utc ? date.setUTCMinutes(value) : date.setMinutes(value);
+          break;
+        }
+        case "second": {
+          const value = Number(part.value);
+          utc ? date.setUTCSeconds(value) : date.setSeconds(value);
+          break;
+        }
+        case "fractionalSecond": {
+          const value = Number(part.value);
+          utc ? date.setUTCMilliseconds(value) : date.setMilliseconds(value);
+          break;
+        }
+      }
+    }
+    return date;
+  }
+
+  parse(string: string): Date {
+    const parts = this.parseToParts(string);
+    return this.partsToDate(parts);
+  }
+}
diff --git a/std/datetime/mod.ts b/std/datetime/mod.ts
index fe6fe6b3c2..e47fe9e01a 100644
--- a/std/datetime/mod.ts
+++ b/std/datetime/mod.ts
@@ -1,7 +1,6 @@
 // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
-import { assert } from "../_util/assert.ts";
 
-export type DateFormat = "mm-dd-yyyy" | "dd-mm-yyyy" | "yyyy-mm-dd";
+import { DateTimeFormatter } from "./formatter.ts";
 
 export const SECOND = 1e3;
 export const MINUTE = SECOND * 60;
@@ -20,92 +19,27 @@ enum Day {
   Sat,
 }
 
-function execForce(reg: RegExp, pat: string): RegExpExecArray {
-  const v = reg.exec(pat);
-  assert(v != null);
-  return v;
-}
 /**
  * Parse date from string using format string
- * @param dateStr Date string
+ * @param dateString Date string
  * @param format Format string
  * @return Parsed date
  */
-export function parseDate(dateStr: string, format: DateFormat): Date {
-  let m, d, y: string;
-  let datePattern: RegExp;
-
-  switch (format) {
-    case "mm-dd-yyyy":
-      datePattern = /^(\d{2})-(\d{2})-(\d{4})$/;
-      [, m, d, y] = execForce(datePattern, dateStr);
-      break;
-    case "dd-mm-yyyy":
-      datePattern = /^(\d{2})-(\d{2})-(\d{4})$/;
-      [, d, m, y] = execForce(datePattern, dateStr);
-      break;
-    case "yyyy-mm-dd":
-      datePattern = /^(\d{4})-(\d{2})-(\d{2})$/;
-      [, y, m, d] = execForce(datePattern, dateStr);
-      break;
-    default:
-      throw new Error("Invalid date format!");
-  }
-
-  return new Date(Number(y), Number(m) - 1, Number(d));
+export function parse(dateString: string, formatString: string): Date {
+  const formatter = new DateTimeFormatter(formatString);
+  const parts = formatter.parseToParts(dateString);
+  return formatter.partsToDate(parts);
 }
 
-export type DateTimeFormat =
-  | "mm-dd-yyyy hh:mm"
-  | "dd-mm-yyyy hh:mm"
-  | "yyyy-mm-dd hh:mm"
-  | "hh:mm mm-dd-yyyy"
-  | "hh:mm dd-mm-yyyy"
-  | "hh:mm yyyy-mm-dd";
-
 /**
- * Parse date & time from string using format string
- * @param dateStr Date & time string
+ * Format date using format string
+ * @param date Date
  * @param format Format string
- * @return Parsed date
+ * @return formatted date string
  */
-export function parseDateTime(
-  datetimeStr: string,
-  format: DateTimeFormat,
-): Date {
-  let m, d, y, ho, mi: string;
-  let datePattern: RegExp;
-
-  switch (format) {
-    case "mm-dd-yyyy hh:mm":
-      datePattern = /^(\d{2})-(\d{2})-(\d{4}) (\d{2}):(\d{2})$/;
-      [, m, d, y, ho, mi] = execForce(datePattern, datetimeStr);
-      break;
-    case "dd-mm-yyyy hh:mm":
-      datePattern = /^(\d{2})-(\d{2})-(\d{4}) (\d{2}):(\d{2})$/;
-      [, d, m, y, ho, mi] = execForce(datePattern, datetimeStr);
-      break;
-    case "yyyy-mm-dd hh:mm":
-      datePattern = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})$/;
-      [, y, m, d, ho, mi] = execForce(datePattern, datetimeStr);
-      break;
-    case "hh:mm mm-dd-yyyy":
-      datePattern = /^(\d{2}):(\d{2}) (\d{2})-(\d{2})-(\d{4})$/;
-      [, ho, mi, m, d, y] = execForce(datePattern, datetimeStr);
-      break;
-    case "hh:mm dd-mm-yyyy":
-      datePattern = /^(\d{2}):(\d{2}) (\d{2})-(\d{2})-(\d{4})$/;
-      [, ho, mi, d, m, y] = execForce(datePattern, datetimeStr);
-      break;
-    case "hh:mm yyyy-mm-dd":
-      datePattern = /^(\d{2}):(\d{2}) (\d{4})-(\d{2})-(\d{2})$/;
-      [, ho, mi, y, m, d] = execForce(datePattern, datetimeStr);
-      break;
-    default:
-      throw new Error("Invalid datetime format!");
-  }
-
-  return new Date(Number(y), Number(m) - 1, Number(d), Number(ho), Number(mi));
+export function format(date: Date, formatString: string): string {
+  const formatter = new DateTimeFormatter(formatString);
+  return formatter.format(date);
 }
 
 /**
diff --git a/std/datetime/test.ts b/std/datetime/test.ts
index d5dccee73c..7e81d30362 100644
--- a/std/datetime/test.ts
+++ b/std/datetime/test.ts
@@ -3,77 +3,103 @@ import { assert, assertEquals, assertThrows } from "../testing/asserts.ts";
 import * as datetime from "./mod.ts";
 
 Deno.test({
-  name: "[std/datetime] parseDate",
+  name: "[std/datetime] parse",
   fn: () => {
     assertEquals(
-      datetime.parseDateTime("01-03-2019 16:30", "mm-dd-yyyy hh:mm"),
+      datetime.parse("01-03-2019 16:30", "MM-dd-yyyy hh:mm"),
       new Date(2019, 0, 3, 16, 30),
     );
     assertEquals(
-      datetime.parseDateTime("03-01-2019 16:31", "dd-mm-yyyy hh:mm"),
+      datetime.parse("01.03.2019 16:30", "MM.dd.yyyy hh:mm"),
+      new Date(2019, 0, 3, 16, 30),
+    );
+    assertEquals(
+      datetime.parse("03-01-2019 16:31", "dd-MM-yyyy hh:mm"),
       new Date(2019, 0, 3, 16, 31),
     );
     assertEquals(
-      datetime.parseDateTime("2019-01-03 16:32", "yyyy-mm-dd hh:mm"),
+      datetime.parse("2019-01-03 16:32", "yyyy-MM-dd hh:mm"),
       new Date(2019, 0, 3, 16, 32),
     );
     assertEquals(
-      datetime.parseDateTime("16:33 01-03-2019", "hh:mm mm-dd-yyyy"),
+      datetime.parse("16:33 01-03-2019", "hh:mm MM-dd-yyyy"),
       new Date(2019, 0, 3, 16, 33),
     );
     assertEquals(
-      datetime.parseDateTime("16:34 03-01-2019", "hh:mm dd-mm-yyyy"),
+      datetime.parse("01-03-2019 16:33:23.123", "MM-dd-yyyy hh:mm:ss.SSS"),
+      new Date(2019, 0, 3, 16, 33, 23, 123),
+    );
+    assertEquals(
+      datetime.parse("01-03-2019 09:33 PM", "MM-dd-yyyy hh:mm a"),
+      new Date(2019, 0, 3, 21, 33),
+    );
+    assertEquals(
+      datetime.parse("16:34 03-01-2019", "hh:mm dd-MM-yyyy"),
       new Date(2019, 0, 3, 16, 34),
     );
     assertEquals(
-      datetime.parseDateTime("16:35 2019-01-03", "hh:mm yyyy-mm-dd"),
+      datetime.parse("16:35 2019-01-03", "hh:mm yyyy-MM-dd"),
       new Date(2019, 0, 3, 16, 35),
     );
+    assertEquals(
+      datetime.parse("01-03-2019", "MM-dd-yyyy"),
+      new Date(2019, 0, 3),
+    );
+    assertEquals(
+      datetime.parse("03-01-2019", "dd-MM-yyyy"),
+      new Date(2019, 0, 3),
+    );
+    assertEquals(
+      datetime.parse("2019-01-03", "yyyy-MM-dd"),
+      new Date(2019, 0, 3),
+    );
   },
 });
 
 Deno.test({
   name: "[std/datetime] invalidParseDateTimeFormatThrows",
   fn: () => {
-    assertThrows(
-      (): void => {
-        // eslint-disable-next-line @typescript-eslint/no-explicit-any
-        (datetime as any).parseDateTime("2019-01-01 00:00", "x-y-z");
-      },
-      Error,
-      "Invalid datetime format!",
-    );
+    assertThrows((): void => {
+      // eslint-disable-next-line @typescript-eslint/no-explicit-any
+      (datetime as any).parse("2019-01-01 00:00", "x-y-z");
+    }, Error);
+    assertThrows((): void => {
+      // eslint-disable-next-line @typescript-eslint/no-explicit-any
+      (datetime as any).parse("2019-01-01", "x-y-z");
+    }, Error);
   },
 });
 
 Deno.test({
-  name: "[std/datetime] parseDate",
+  name: "[std/datetime] format",
   fn: () => {
     assertEquals(
-      datetime.parseDate("01-03-2019", "mm-dd-yyyy"),
-      new Date(2019, 0, 3),
+      "2019-01-01",
+      datetime.format(new Date("2019-01-01T03:24:00"), "yyyy-MM-dd"),
     );
     assertEquals(
-      datetime.parseDate("03-01-2019", "dd-mm-yyyy"),
-      new Date(2019, 0, 3),
+      "01.01.2019",
+      datetime.format(new Date("2019-01-01T03:24:00"), "dd.MM.yyyy"),
     );
     assertEquals(
-      datetime.parseDate("2019-01-03", "yyyy-mm-dd"),
-      new Date(2019, 0, 3),
+      "03:24:00",
+      datetime.format(new Date("2019-01-01T03:24:00"), "hh:mm:ss"),
     );
-  },
-});
-
-Deno.test({
-  name: "[std/datetime] invalidParseDateFormatThrows",
-  fn: () => {
-    assertThrows(
-      (): void => {
-        // eslint-disable-next-line @typescript-eslint/no-explicit-any
-        (datetime as any).parseDate("2019-01-01", "x-y-z");
-      },
-      Error,
-      "Invalid date format!",
+    assertEquals(
+      "03:24:00.532",
+      datetime.format(new Date("2019-01-01T03:24:00.532"), "hh:mm:ss.SSS"),
+    );
+    assertEquals(
+      "03:24:00 AM",
+      datetime.format(new Date("2019-01-01T03:24:00"), "hh:mm:ss a"),
+    );
+    assertEquals(
+      "09:24:00 PM",
+      datetime.format(new Date("2019-01-01T21:24:00"), "hh:mm:ss a"),
+    );
+    assertEquals(
+      datetime.format(new Date(2019, 0, 20), "'today:' yyyy-MM-dd"),
+      "today: 2019-01-20",
     );
   },
 });
@@ -186,7 +212,7 @@ Deno.test({
 });
 
 Deno.test({
-  name: "[std/datetime] Difference",
+  name: "[std/datetime] difference",
   fn(): void {
     const denoInit = new Date("2018/5/14");
     const denoRelaseV1 = new Date("2020/5/13");
diff --git a/std/datetime/tokenizer.ts b/std/datetime/tokenizer.ts
new file mode 100644
index 0000000000..05314b7706
--- /dev/null
+++ b/std/datetime/tokenizer.ts
@@ -0,0 +1,69 @@
+export type Token = {
+  type: string;
+  value: string | number;
+  index: number;
+};
+
+interface ReceiverResult {
+  [name: string]: string | number;
+}
+export type CallbackResult = { type: string; value: string | number };
+type CallbackFunction = (value: unknown) => CallbackResult;
+
+export type TestResult = { value: unknown; length: number } | undefined;
+export type TestFunction = (
+  string: string,
+) => TestResult | undefined;
+
+export interface Rule {
+  test: TestFunction;
+  fn: CallbackFunction;
+}
+
+export class Tokenizer {
+  rules: Rule[];
+
+  constructor(rules: Rule[] = []) {
+    this.rules = rules;
+  }
+
+  addRule(test: TestFunction, fn: CallbackFunction): Tokenizer {
+    this.rules.push({ test, fn });
+    return this;
+  }
+
+  tokenize(
+    string: string,
+    receiver = (token: Token): ReceiverResult => token,
+  ): ReceiverResult[] {
+    function* generator(rules: Rule[]): IterableIterator<ReceiverResult> {
+      let index = 0;
+      for (const rule of rules) {
+        const result = rule.test(string);
+        if (result) {
+          const { value, length } = result;
+          index += length;
+          string = string.slice(length);
+          const token = { ...rule.fn(value), index };
+          yield receiver(token);
+          yield* generator(rules);
+        }
+      }
+    }
+    const tokenGenerator = generator(this.rules);
+
+    const tokens: ReceiverResult[] = [];
+
+    for (const token of tokenGenerator) {
+      tokens.push(token);
+    }
+
+    if (string.length) {
+      throw new Error(
+        `parser error: string not fully parsed! ${string.slice(0, 25)}`,
+      );
+    }
+
+    return tokens;
+  }
+}