// for time zones, see here: https://en.wikipedia.org/wiki/List_of_time_zone_abbreviations#cite_note-18 // and see if you can scrape them to make a working parser. /** * Millisecond is the base duration unit. */ export const Millisecond = 1; /** * Second = 1000 * Millisecond */ export const Second = 1000 * Millisecond; /** * Minute = 60 * Second */ export const Minute = 60 * Second; /** * Hour = 60 * Minute */ export const Hour = 60 * Minute; /** * These are predefined layouts for use in Time.Format and time.Parse. * The reference time used in these layouts is the specific time stamp: * * 01/02 03:04:05PM '06 -0700 * * (January 2, 15:04:05, 2006, in time zone seven hours west of GMT). * That value is recorded as the constant named Layout, listed below. As a * Unix time, this is 1136239445. Since MST is GMT-0700, the reference would be * printed by the Unix date command as: * * Mon Jan 2 15:04:05 MST 2006 * * It is a regrettable historic error that the date uses the American * convention of putting the numerical month before the day. * * The example for Time.Format demonstrates the working of the layout string in * detail and is a good reference. * * Note that the RFC822, RFC850, and RFC1123 formats should be applied only * to local times. Applying them to UTC times will use "UTC" as the time zone * abbreviation, while strictly speaking those RFCs require the use of "GMT" * in that case. When using the RFC1123 or RFC1123Z formats for parsing, * note that these formats define a leading zero for the day-in-month portion, * which is not strictly allowed by RFC 1123. This will result in an error * when parsing date strings that occur in the first 9 days of a given month. * In general RFC1123Z should be used instead of RFC1123 for servers that * insist on that format, and RFC3339 should be preferred for new protocols. * RFC3339, RFC822, RFC822Z, RFC1123, and RFC1123Z are useful for formatting; * when used with time.Parse they do not accept all the time formats permitted * by the RFCs and they do accept time formats not formally defined. The * RFC3339Nano format removes trailing zeros from the seconds field and thus * may not sort correctly once formatted. * * Most programs can use one of the defined constants as the layout passed * to Format or Parse. The rest of this comment can be ignored unless you are * creating a custom layout string. * * To define your own format, write down what the reference time would look * like formatted your way; see the values of constants like ANSIC, StampMicro * or Kitchen for examples. The model is to demonstrate what the reference * time looks like so that the Format and Parse methods can apply the same * transformation to a general time value. * * Here is a summary of the components of a layout string. Each element shows * by example the formatting of an element of the reference time. Only these * values are recognized. Text in the layout string that is not recognized as * part of the reference time is echoed verbatim during Format and expected to * appear verbatim in the input to Parse. * * Year: "2006" "06" * Month: "Jan" "January" "01" "1" * Day of the week: "Mon" "Monday" * Day of the month: "2" "_2" "02" * Day of the year: "__2" "002" * Hour: "15" "3" "03" (PM or AM) * Minute: "4" "04" * Second: "5" "05" * AM/PM mark: "PM" * * Numeric time zone offsets format as follows: * * "-0700" ±hhmm * "-07:00" ±hh:mm * "-07" ±hh * "-070000" ±hhmmss * "-07:00:00" ±hh:mm:ss * * Replacing the sign in the format with a Z triggers the ISO 8601 behavior of * printing Z instead of an offset for the UTC zone. Thus: * * "Z0700" Z or ±hhmm * "Z07:00" Z or ±hh:mm * "Z07" Z or ±hh * "Z070000" Z or ±hhmmss * "Z07:00:00" Z or ±hh:mm:ss * * Within the format string, the underscores in "_2" and "__2" represent spaces * that may be replaced by digits if the following number has multiple digits, * for compatibility with fixed-width Unix time formats. A leading zero * represents a zero-padded value. * * The formats __2 and 002 are space-padded and zero-padded three-character day * of year; there is no unpadded day of year format. * * A comma or decimal point followed by one or more zeros represents a * fractional second, printed to the given number of decimal places. A comma or * decimal point followed by one or more nines represents a fractional second, * printed to the given number of decimal places, with trailing zeros removed. * For example "15:04:05,000" or "15:04:05.000" formats or parses with * millisecond precision. * * Some valid layouts are invalid time values for time.Parse, due to formats * such as _ for space padding and Z for zone information. */ export const Layout = "01/02 03:04:05PM '06 -0700"; // The reference time, in numerical order. export const ANSIC = "Mon Jan _2 15:04:05 2006"; export const UnixDate = "Mon Jan _2 15:04:05 MST 2006"; export const RubyDate = "Mon Jan 02 15:04:05 -0700 2006"; export const RFC822 = "02 Jan 06 15:04 MST"; export const RFC822Z = "02 Jan 06 15:04 -0700"; // RFC822 with numeric zone export const RFC850 = "Monday, 02-Jan-06 15:04:05 MST"; export const RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST"; export const RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700"; // RFC1123 with numeric zone export const RFC3339 = "2006-01-02T15:04:05Z07:00"; export const RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"; export const Kitchen = "3:04PM"; export const Stamp = "Jan _2 15:04:05"; export const StampMilli = "Jan _2 15:04:05.000"; export const StampMicro = "Jan _2 15:04:05.000000"; export const StampNano = "Jan _2 15:04:05.000000000"; export const DateTime = "2006-01-02 15:04:05"; export const DateOnly = "2006-01-02"; export const TimeOnly = "15:04:05"; /** * Local represents the system's local time zone. */ export const Local = String(Intl.DateTimeFormat().resolvedOptions().timeZone); /** * UTC represents Universal Coordinated Time (UTC). */ export const UTC = "Etc/UTC"; /** * List available locations/IANA names. */ export function ListAvailableIANAs() { // @ts-ignore return Intl.supportedValuesOf("timeZone"); } /** * A Month specifies a month of the year (January = 1, ...). */ export var Month; (function (Month) { Month[Month["January"] = 1] = "January"; Month[Month["February"] = 2] = "February"; Month[Month["March"] = 3] = "March"; Month[Month["April"] = 4] = "April"; Month[Month["May"] = 5] = "May"; Month[Month["June"] = 6] = "June"; Month[Month["July"] = 7] = "July"; Month[Month["August"] = 8] = "August"; Month[Month["September"] = 9] = "September"; Month[Month["October"] = 10] = "October"; Month[Month["November"] = 11] = "November"; Month[Month["December"] = 12] = "December"; })(Month || (Month = {})); /** * A Weekday specifies a day of the week (Sunday = 0, ...). */ export var Weekday; (function (Weekday) { Weekday[Weekday["Sunday"] = 0] = "Sunday"; Weekday[Weekday["Monday"] = 1] = "Monday"; Weekday[Weekday["Tuesday"] = 2] = "Tuesday"; Weekday[Weekday["Wednesday"] = 3] = "Wednesday"; Weekday[Weekday["Thursday"] = 4] = "Thursday"; Weekday[Weekday["Friday"] = 5] = "Friday"; Weekday[Weekday["Saturday"] = 6] = "Saturday"; })(Weekday || (Weekday = {})); /** * FromJSDate to convert a javascript Date object to Time. */ export function FromJSDate(jsDate) { return new _Time(jsDate.getTime(), UTCLocation); } /** * Now returns the current local time. */ export function Now() { return new _Time(Date.now(), LocalLocation); } /** * Unix returns the local Time corresponding to the given Unix time, * sec seconds since January 1, 1970 UTC. */ export function Unix(seconds) { return new _Time(seconds * 1000, LocalLocation); } /** * UnixMilli returns the local Time corresponding to the given Unix time, * msec milliseconds since January 1, 1970 UTC. */ export function UnixMilli(millis) { return new _Time(millis, LocalLocation); } /** * Since returns the time elapsed since t. It is shorthand for * time.Now().Sub(t). */ export function Since(t) { return Now().Sub(t); } /** * Until returns the duration until t. It is shorthand for t.Sub(time.Now()). */ export function Until(t) { return t.Sub(Now()); } /** * Replicates golangs time.Date(...) function for creating Time objects. * note that nanoseconds is replaced with milliseconds for javascript * and the name is DateAt (to avoid conflict with JS built-in Date). */ export function DateAt(year, month, day, hour, min, sec, milli, loc) { return _DateAt(year, month, day, hour, min, sec, milli, loc); } /** * ParseInLocation is like Parse but in the absence of time zone information, * Parse interprets a time as UTC and ParseInLocation interprets the time * as in the given location. Unlike go, no attempt is made to match an abbreviation * inside the given timezone. Location should be a valid IANA timezone identifier. */ export function ParseInLocation(layout, value, location) { return parseInternal(layout, value, location); } /** * Parse parses a formatted string and returns the time value it represents. * See the documentation for the constant called Layout to see how to represent * the format. The second argument must be parseable using the format string * (layout) provided as the first argument. */ export function Parse(layout, value) { return parseInternal(layout, value, UTC); } class _Time { #unixMilli; // always in UTC. #location; #displayInfo = void 0; constructor(utcUnixMilli, loc) { this.#unixMilli = utcUnixMilli; this.#location = loc; } #getDisplayInfo() { if (this.#displayInfo) return this.#displayInfo; const info = _displayInfoFor(this.#unixMilli, this.#location); this.#displayInfo = info; return info; } UnixMilli() { // returns milliseconds. return this.#unixMilli; } Add(d) { return new _Time(this.#unixMilli + d, this.#location); } In(location) { return new _Time(this.#unixMilli, new _IANALocation(location)); } Clock() { const { hour, minute, second } = this.#getDisplayInfo(); return { hour, minute, second }; } Date() { const { year, month, day } = this.#getDisplayInfo(); return { year, month, day }; } Weekday() { return this.#getDisplayInfo().weekdayInt; } YearDay() { const { year, month, day } = this.Date(); return yearDay(year, month, day); } Year() { return this.Date().year; } Month() { return this.Date().month; } Day() { return this.Date().day; } Hour() { return this.Clock().hour; } Minute() { return this.Clock().minute; } Second() { return this.Clock().second; } Millisecond() { return this.#getDisplayInfo().millisecond; } Zone() { const { timeZoneName, timeZoneOffset } = this.#getDisplayInfo(); return { name: timeZoneName, offset: Math.trunc(timeZoneOffset / Second) }; } UTC() { return this.In(UTC); } Local() { return this.In(Local); } JSDate() { return new Date(this.#unixMilli); } String() { return this.Format("2006-01-02 15:04:05.999999999 -0700 MST"); } Unix() { return Math.floor(this.#unixMilli / 1000); } After(u) { return this.#unixMilli > u.UnixMilli(); } Before(u) { return this.#unixMilli < u.UnixMilli(); } Equal(u) { return this.#unixMilli === u.UnixMilli(); } Format(layout) { return formatInternal(this.#getDisplayInfo(), layout); } Sub(u) { return this.#unixMilli - u.UnixMilli(); } } function _DateAt(year, month, day, hour, min, sec, milli, loc) { const location = loc === UTC ? UTCLocation : new _IANALocation(loc); return _DateAtLocation(year, month, day, hour, min, sec, milli, location); } function _DateAtLocation(year, month, day, hour, min, sec, milli, loc) { const utcUnixMs = Date.UTC(year, month - 1, day, hour, min, sec, milli); if (loc === UTCLocation) return new _Time(utcUnixMs, UTCLocation); // 1. Build unix millis, // 2. create date, // 3. adjust based on locale. const offset1 = loc.OffsetAt(utcUnixMs); const offset2 = loc.OffsetAt(utcUnixMs - offset1); if (offset1 === offset2) { return new _Time(utcUnixMs - offset1, loc); } return new _Time(utcUnixMs - offset2, loc); } class _FixedLocation { #name; #offset; constructor(name, offset) { this.#name = name; this.#offset = offset; } AbbrvAt(_unixMilli) { return this.#name; } OffsetAt(_unixMilli) { return this.#offset; } } class _IANALocation { #iana; constructor(iana) { this.#iana = iana; } AbbrvAt(unixMilli) { return _getAbbrvAt(unixMilli, this.#iana); } OffsetAt(unixMilli) { return _getOffsetAt(unixMilli, this.#iana); } } const LocalLocation = new _IANALocation(Local); const UTCLocation = new _IANALocation(UTC); /** * _GMTOffsetToDuration: takes GMT+03:00 -> (3 * 60 * 60) * 1000 (milliseconds offset) */ function _GMTOffsetToDuration(offsetStr) { if (offsetStr === "GMT") { return 0; } const throwErr = () => { throw new Error(`invalid GMT offset format '${offsetStr}'`); }; if (offsetStr.substring(0, 3) !== "GMT") throwErr(); const subs = offsetStr.substring(3); let sign = 1, hours = 0, minutes = 0, seconds = 0; const signChar = subs[0]; if (signChar === "+") { sign = 1; } else if (signChar === "-") { sign = -1; } else { throwErr(); } let i = 1; const [hrs, hrsL] = getnum(subs.substring(i), true); i += hrsL; hours = hrs; if (subs[i] === ":") { i++; const [mins, minL] = getnum(subs.substring(i), true); minutes = mins; i += minL; } if (subs[i] == ":") { i++; const [secs, secL] = getnum(subs.substring(i), true); seconds = secs; i += secL; } return sign * (hours * Hour + minutes * Minute + seconds * Second); } function _getOffsetAt(unixMilli, iana) { const intl = new Intl.DateTimeFormat("en-US", { timeZone: iana, timeZoneName: "longOffset", }).formatToParts(new Date(unixMilli)); for (const item of intl) { if (item.type === "timeZoneName") return _GMTOffsetToDuration(item.value); } throw new Error(`offset not found for: ${iana}`); } function _getAbbrvAt(unixMilli, iana) { const intl = new Intl.DateTimeFormat("en-US", { timeZone: iana, timeZoneName: "short", }).formatToParts(new Date(unixMilli)); for (const item of intl) { if (item.type === "timeZoneName") return item.value; } throw new Error(`time zone abbreviation not found for: ${iana}`); } // get the display information for a unix time + location. // we use the location to get the offset information. // then shift the date and get the display info (like year, month, day, hour, min, second, weekday, etcetera) by pretending that we are in UTC. function _displayInfoFor(unixMilli, loc) { const abbrv = loc.AbbrvAt(unixMilli); const offset = loc.OffsetAt(unixMilli); const fakeUTCUnix = unixMilli + offset; const fakeDate = new Date(fakeUTCUnix); return { year: fakeDate.getUTCFullYear(), month: fakeDate.getUTCMonth() + 1, weekday: longDayNames[fakeDate.getUTCDay()], weekdayInt: fakeDate.getUTCDay(), day: fakeDate.getUTCDate(), hour: fakeDate.getUTCHours(), minute: fakeDate.getUTCMinutes(), second: fakeDate.getUTCSeconds(), millisecond: fakeDate.getUTCMilliseconds(), timeZoneName: abbrv, timeZoneOffset: offset, }; } // function formatInternal(t: _Time, layout: string): string { function formatInternal(info, layout) { const out = []; const parts = parseLayout(layout, false); const { year, month, day, weekday, hour, minute, second, millisecond, timeZoneName, timeZoneOffset, } = info; const zoneOffset = splitDuration(timeZoneOffset); const monthIndex = month - 1; for (const p of parts) { if (typeof p === "string") { out.push(p); continue; } switch (65535 /* std.MaskLower */ & p) { case 0 /* std.LongMonth */: out.push(longMonthNames[monthIndex]); break; case 1 /* std.Month */: out.push(shortMonthNames[monthIndex]); break; case 2 /* std.NumMonth */: out.push(String(month)); break; case 3 /* std.ZeroMonth */: out.push(String(month).padStart(2, "0")); break; case 4 /* std.LongWeekDay */: out.push(weekday); break; case 5 /* std.WeekDay */: out.push(weekday.substr(0, 3)); break; case 6 /* std.Day */: out.push(String(day)); break; case 7 /* std.UnderDay */: out.push(String(day).padStart(2, " ")); break; case 8 /* std.ZeroDay */: out.push(String(day).padStart(2, "0")); break; case 9 /* std.UnderYearDay */: out.push(String(yearDay(year, month, day)).padStart(3, " ")); break; case 10 /* std.ZeroYearDay */: out.push(String(yearDay(year, month, day)).padStart(3, "0")); break; case 11 /* std.Hour */: out.push(String(hour).padStart(2, "0")); break; case 12 /* std.Hour12 */: out.push(String(hour % 12 || 12)); break; case 13 /* std.ZeroHour12 */: out.push(String(hour % 12 || 12).padStart(2, "0")); break; case 14 /* std.Minute */: out.push(String(minute)); break; case 15 /* std.ZeroMinute */: out.push(String(minute).padStart(2, "0")); break; case 16 /* std.Second */: out.push(String(second)); break; case 17 /* std.ZeroSecond */: out.push(String(second).padStart(2, "0")); break; case 18 /* std.LongYear */: out.push(String(year)); break; case 19 /* std.Year */: out.push(String(year).substring(2)); break; case 20 /* std.PM */: out.push(hour >= 12 ? "PM" : "AM"); break; case 21 /* std.pm */: out.push(hour >= 12 ? "pm" : "am"); break; case 22 /* std.TZ */: if (timeZoneName.length > 0) { out.push(timeZoneName); } else { // No time zone known for this time, but we must print one. // Use the -0700 format. out.push(zoneOffset.sign < 0 ? "-" : "+"); out.push(String(zoneOffset.hours).padStart(2, "0")); out.push(String(zoneOffset.minutes).padStart(2, "0")); } break; case 23 /* std.ISO8601TZ */: if (timeZoneOffset === 0) { out.push("Z"); break; } // fallthrough case 28 /* std.NumTZ */: out.push(zoneOffset.sign < 0 ? "-" : "+"); out.push(String(zoneOffset.hours).padStart(2, "0")); out.push(String(zoneOffset.minutes).padStart(2, "0")); break; case 24 /* std.ISO8601SecondsTZ */: if (timeZoneOffset === 0) { out.push("Z"); break; } // fallthrough case 29 /* std.NumSecondsTZ */: out.push(zoneOffset.sign < 0 ? "-" : "+"); out.push(String(zoneOffset.hours).padStart(2, "0")); out.push(String(zoneOffset.minutes).padStart(2, "0")); out.push(String(zoneOffset.seconds).padStart(2, "0")); break; case 25 /* std.ISO8601ShortTZ */: if (timeZoneOffset === 0) { out.push("Z"); break; } // fallthrough case 30 /* std.NumShortTZ */: out.push(zoneOffset.sign < 0 ? "-" : "+"); out.push(String(zoneOffset.hours).padStart(2, "0")); break; case 26 /* std.ISO8601ColonTZ */: if (timeZoneOffset === 0) { out.push("Z"); break; } // fallthrough case 31 /* std.NumColonTZ */: out.push(zoneOffset.sign < 0 ? "-" : "+"); out.push(String(zoneOffset.hours).padStart(2, "0")); out.push(":"); out.push(String(zoneOffset.minutes).padStart(2, "0")); break; case 27 /* std.ISO8601ColonSecondsTZ */: if (timeZoneOffset === 0) { out.push("Z"); break; } //fallthroguh case 32 /* std.NumColonSecondsTZ */: out.push(zoneOffset.sign < 0 ? "-" : "+"); out.push(String(zoneOffset.hours).padStart(2, "0")); out.push(":"); out.push(String(zoneOffset.minutes).padStart(2, "0")); out.push(":"); out.push(String(zoneOffset.seconds).padStart(2, "0")); break; case 33 /* std.FracSecond0 */: out.push(fracSecondsStr(p, millisecond)); break; case 34 /* std.FracSecond9 */: const fracS = fracSecondsStr(p, millisecond); if (fracS.length === 0) { out.pop(); // remove the comma/period. break; } out.push(fracS); break; default: console.warn("UNHANDLED", p); break; } } return out.join(""); } function parseInternal(layout, value, defaultLocation) { const parts = parseLayout(layout, true); let year = 0, month = -1, day = -1, yearday = -1, hour = 0, minute = 0, second = 0, milli = 0, pmSet = false, amSet = false, tzString = "", zone = null, zoneOffsetHr = 0, zoneOffsetMin = 0, zoneOffsetSec = 0, zoneOffsetSign = 1; let vi = 0; for (let partIndex = 0; partIndex < parts.length; partIndex++) { const p = parts[partIndex]; if (typeof p === "string") { if (p === " ") { while (value[vi] === " ") vi++; } else { vi += p.length; } continue; } const mP = p & 65535 /* std.MaskLower */; // console.log(std[mP], `'${value.substring(vi)}'`, vi); switch (mP) { case 1 /* std.Month */: // fallthrough case 0 /* std.LongMonth */: const [monthS, monthIndex] = (mP === 1 /* std.Month */ ? getShortMonthPrefix : getLongMonthPrefix)(value.substring(vi)); month = monthIndex + 1; vi += monthS.length; break; case 2 /* std.NumMonth */: // fallthrough case 3 /* std.ZeroMonth */: const [nm, nml] = getnum(value.substring(vi), mP === 3 /* std.ZeroMonth */); if (nm <= 0 || nm > 12) throw new Error(`invalid month ${nm}: ${value}`); month = nm; vi += nml; break; case 4 /* std.LongWeekDay */: vi += getLongDayPrefix(value.substring(vi))[0].length; break; case 5 /* std.WeekDay */: vi += getShortDayPrefix(value.substring(vi))[0].length; break; case 6 /* std.Day */: // fallthrough case 7 /* std.UnderDay */: if (value[vi] === " ") vi++; // fallthrough case 8 /* std.ZeroDay */: const [dayVal, dayLength] = getnum(value.substring(vi), mP === 8 /* std.ZeroDay */); day = dayVal; vi += dayLength; break; case 9 /* std.UnderYearDay */: case 10 /* std.ZeroYearDay */: { for (let ydi = 0; ydi < 2; ydi++) { if (mP === 9 /* std.UnderYearDay */ && vi < value.length && value[0] == " ") { vi++; } } const [yval, ylen] = getnum3(value.substring(vi), mP === 10 /* std.ZeroYearDay */); vi += ylen; yearday = yval; break; } case 11 /* std.Hour */: { const [hourNum, hourNumL] = getnum(value.substring(vi), false); if (hourNum < 0 || 24 <= hourNum) throw new Error(`invalid hour ${hourNum}`); hour = hourNum; vi += hourNumL; break; } case 12 /* std.Hour12 */: // fallthrough case 13 /* std.ZeroHour12 */: { const [hourNum, hourNumL] = getnum(value.substring(vi), mP == 13 /* std.ZeroHour12 */); if (hourNum < 0 || 12 < hourNum) throw new Error(`invalid hour12: ${hourNum}`); hour = hourNum; vi += hourNumL; break; } case 14 /* std.Minute */: // fallthrough case 15 /* std.ZeroMinute */: const [minNum, minNumL] = getnum(value.substring(vi), mP === 15 /* std.ZeroMinute */); if (minNum < 0 || minNum > 60) throw new Error(`invalid minute: ${minNum}`); minute = minNum; vi += minNumL; break; case 16 /* std.Second */: case 17 /* std.ZeroSecond */: { const [secNum, setNumL] = getnum(value.substring(vi), mP === 17 /* std.ZeroSecond */); if (secNum < 0 || secNum > 60) throw new Error(`invalid second: ${secNum}`); second = secNum; vi += setNumL; // Special case: do we have a fractional second but no // fractional second in the format? const subv = value.substring(vi); if (subv.length >= 2 && isCommaOrPeriod(subv[0]) && isDigit(subv[1])) { const nextP = parts[partIndex + 1]; const nextMP = typeof nextP === "string" ? -1 : nextP & 65535 /* std.MaskLower */; if (nextMP === 33 /* std.FracSecond0 */ || nextMP == 34 /* std.FracSecond9 */) { // Fractional second in the layout; proceed normally break; } // No fractional second in the layout but we have one in the input. let n = 2; while (n < subv.length && isDigit(subv[n])) { n++; } const [millisV, millisL] = parseFracSeconds(subv.substring(0, n)); milli = millisV; vi += millisL; } break; } case 18 /* std.LongYear */: // fallthrough case 19 /* std.Year */: const yearS = value.substring(vi, vi + (mP === 19 /* std.Year */ ? 2 : 4)); throwInvalidNumber(yearS, `year in '${value}'`); year = Number(yearS); if (mP === 19 /* std.Year */) { if (year >= 69) { year += 1900; } else { year += 2000; } } vi += yearS.length; break; case 20 /* std.PM */: { const pmS = value.substring(vi, vi + 2); if (pmS === "PM") { pmSet = true; } else if (pmS == "AM") { amSet = true; } else { throw new Error(`invalid AM/PM: ${pmS}`); } vi += pmS.length; break; } case 21 /* std.pm */: { const pmS = value.substring(vi, vi + 2); if (pmS === "pm") { pmSet = true; } else if (pmS == "am") { amSet = true; } else { throw new Error(`invalid AM/PM: ${pmS}`); } vi += pmS.length; break; } case 22 /* std.TZ */: tzString = parseTimeZoneStr(value.substring(vi)); vi += tzString.length; if (tzString === "UTC") zone = UTC; break; case 23 /* std.ISO8601TZ */: case 25 /* std.ISO8601ShortTZ */: case 26 /* std.ISO8601ColonTZ */: case 24 /* std.ISO8601SecondsTZ */: case 27 /* std.ISO8601ColonSecondsTZ */: if (value[vi] === "Z") { zone = UTC; vi++; break; } case 28 /* std.NumTZ */: case 30 /* std.NumShortTZ */: case 31 /* std.NumColonTZ */: case 29 /* std.NumSecondsTZ */: case 32 /* std.NumColonSecondsTZ */: let zsign = "", zhour = "", zmin = "", zsec = ""; const subv = value.substring(vi); const errTooShort = () => { throw new Error(`tz too short: '${subv}'`); }; if (mP === 26 /* std.ISO8601ColonTZ */ || mP === 31 /* std.NumColonTZ */) { if (subv.length < 6) errTooShort(); if (subv[3] !== ":") throw new Error(`tz missing colon: ${subv[3]} (${subv} -- ${value})`); zsign = subv[0]; zhour = subv.substring(1, 3); zmin = subv.substring(4, 6); zsec = "00"; vi += 6; } else if (mP === 30 /* std.NumShortTZ */ || mP == 25 /* std.ISO8601ShortTZ */) { if (subv.length < 3) errTooShort(); zsign = subv[0]; zhour = subv.substring(1, 3); zmin = "00"; zsec = "00"; vi += 3; } else if (mP === 27 /* std.ISO8601ColonSecondsTZ */ || mP == 32 /* std.NumColonSecondsTZ */) { if (subv.length < 9) errTooShort(); if (subv[3] !== ":" || subv[6] !== ":") throw new Error(`tz missing colo: ${subv[3]} ${subv[6]}`); zsign = subv[0]; zhour = subv.substring(1, 3); zmin = subv.substring(4, 6); zsec = subv.substring(7, 9); vi += 9; } else if (mP === 24 /* std.ISO8601SecondsTZ */ || mP === 29 /* std.NumSecondsTZ */) { if (subv.length < 7) errTooShort(); zsign = subv[0]; zhour = subv.substring(1, 3); zmin = subv.substring(3, 5); zsec = subv.substring(5, 7); vi += 7; } else { if (subv.length < 5) errTooShort(); zsign = subv[0]; zhour = subv.substring(1, 3); zmin = subv.substring(3, 5); zsec = "00"; vi += 5; } const zhr = getnum(zhour, true)[0]; const zmm = getnum(zmin, true)[0]; const zss = getnum(zsec, true)[0]; if (zhr > 24) throw new Error(`invalid offset hour: ${zhr}`); if (zmm > 60) throw new Error(`invalid offset minute: ${zmm}`); if (zss > 60) throw new Error(`invalid offset second: ${zss}`); zoneOffsetHr = zhr; zoneOffsetMin = zmm; zoneOffsetSec = zss; if (zsign === "+") { zoneOffsetSign = 1; } else if (zsign === "-") { zoneOffsetSign = -1; } else { throw new Error(`invalid zone sign: '${zsign}'`); } break; case 34 /* std.FracSecond9 */: { const subv = value.substring(vi); if (subv.length < 2) break; if (!(isCommaOrPeriod(subv[0]) && isDigit(subv[1]))) break; let endIndex = 1; while (endIndex < subv.length && isDigit(subv[endIndex])) { endIndex++; } const [millisV, millisL] = parseFracSeconds(subv.substring(0, endIndex)); vi += millisL; milli = millisV; break; } case 33 /* std.FracSecond0 */: { const numDigits = (p >> 16) & 0xfff; const subv = value.substring(vi); // exact num digits. if (subv.length < numDigits + 1) { throw new Error(`not enough digits ${subv}`); } const [millisV, millisL] = parseFracSeconds(subv.substring(0, numDigits + 1)); vi += millisL; milli = millisV; break; } default: console.warn(`unhandled`, p); break; } } if (pmSet && hour < 12) { hour += 12; } else if (amSet && hour == 12) { hour = 0; } if (yearday >= 0) { // console.log({ yearday }); let monthTemp = 0, dayTemp = 0; if (isLeap(year)) { if (yearday === 31 + 29) { monthTemp = Month.February; dayTemp = 29; } else if (yearday > 31 + 29) { yearday--; } } if (yearday < 1 || yearday > 365) { throw new Error(`invalid year day: ${yearday}`); } if (monthTemp === 0) { monthTemp = Math.trunc((yearday - 1) / 31) + 1; if (daysBefore(monthTemp + 1) < yearday) { monthTemp++; } dayTemp = yearday - daysBefore(monthTemp); } if (month >= 0 && month !== monthTemp) { throw new Error(`day-of-year does not match month`); } month = monthTemp; if (day >= 0 && day !== dayTemp) { throw new Error(`day-of-year does not match day`); } day = dayTemp; } else { if (month < 0) month = Month.January; if (day < 0) day = 1; } // console.log({ year, month, day, hour, minute, second, milli }); if (day < 1 || day > daysIn(month, year)) { throw new Error(`day ${day} out of range for month+year`); } if (zone !== null) { // handles UTC return _DateAt(year, month, day, hour, minute, second, milli, zone); } if (zoneOffsetHr !== 0 || zoneOffsetMin !== 0 || zoneOffsetSec !== 0) { const offsetSec = zoneOffsetSign * (zoneOffsetHr * 60 + zoneOffsetMin) * 60 + zoneOffsetSec; const loc = new _FixedLocation(tzString, offsetSec * Second); return _DateAtLocation(year, month, day, hour, minute, second, milli, loc); } if (tzString.length > 0) { const t = _DateAtLocation(year, month, day, hour, minute, second, milli, UTCLocation); let offset = 0; if (tzString.length > 3 && tzString.substring(0, 3) === "GMT") { offset = throwInvalidNumber(tzString.substring(3), tzString); // like GMT-8, GMT+8 } const loc = new _FixedLocation(tzString, offset * Hour); return new _Time(t.UnixMilli(), loc); } return _DateAt(year, month, day, hour, minute, second, milli, defaultLocation); } function daysBefore(m) { let adj = 0; if (m >= Month.March) { adj = -2; } // With the -2 adjustment after February, // we need to compute the running sum of: // 0 31 30 31 30 31 30 31 31 30 31 30 31 // which is: // 0 31 61 92 122 153 183 214 245 275 306 336 367 // This is almost exactly 367/12×(m-1) except for the // occasonal off-by-one suggesting there may be an // integer approximation of the form (a×m + b)/c. // A brute force search over small a, b, c finds that // (214×m - 211) / 7 computes the function perfectly. // return (214*int(m)-211)/7 + adj return Math.trunc((214 * m - 211) / 7) + adj; } const longDayNames = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", ]; const shortDayNames = [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", ]; const shortMonthNames = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", ]; const longMonthNames = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", ]; function getnum3(s, fixed) { let n = 0, i = 0; while (i < 3 && isDigit(s[i])) { n = n * 10 + throwInvalidNumber(s[i], "year day"); i++; } if (i === 0 || (fixed && i !== 3)) { throw new Error(`not enough digits: '${s}' ${fixed}`); } return [n, i]; } // get 2 digit num function getnum(s, fixed) { if (!isDigit(s[0])) { throw new Error(`not a digit: '${s[0]}' (${s})`); } if (!isDigit(s[1])) { if (fixed) { throw new Error(`not a digit: '${s[1]}' (${s})`); } return [Number(s[0]), 1]; } return [Number(s.substring(0, 2)), 2]; } function _findPrefix(s, opts, typeName) { for (let i = 0; i < opts.length; i++) { const opt = opts[i]; if (s.toLowerCase().startsWith(opt.toLowerCase())) { return [opt, i]; } } throw new Error(`invalid ${typeName}: '${s}'`); } function getShortDayPrefix(s) { return _findPrefix(s, shortDayNames, "short day name"); } function getLongDayPrefix(s) { return _findPrefix(s, longDayNames, "long day name"); } function getShortMonthPrefix(s) { return _findPrefix(s, shortMonthNames, "short month name"); } function getLongMonthPrefix(s) { return _findPrefix(s, longMonthNames, "long month name"); } function parseSignedOffset(s) { const sign = s[0]; if (sign !== "+" && sign !== "-") { throw new Error(`bad signed tz offset ${s}`); } let i = 1; while (i < s.length && isDigit(s[i])) { i++; } return s.substring(0, i); } function parseTimeZoneStr(s) { const _doThrow = () => { throw new Error(`invalid time zone: '${s}'`); }; if (s.length < 3) { _doThrow(); } const first3 = s.substring(0, 3); if (first3 === "UTC") { return first3; } // Special case 1: ChST and MeST are the only zones with a lower-case letter. const first4 = s.substring(0, 4); if (first4.length === 4 && (first4 === "ChST" || first4 === "MeST")) { return first4; } // Special case 2: GMT may have an hour offset; treat it specially. if (first3 === "GMT") { if (first4[3] === "+" || first4[3] === "-") { return first3 + parseSignedOffset(s.substring(3)); } return first3; } if (s[0] === "+" || s[1] === "-") { return parseSignedOffset(s); } let nUpper = 0; while (nUpper < 6) { if (nUpper >= s.length) { break; } if (!isUppercase(s[nUpper])) { break; } nUpper++; } switch (nUpper) { case 0: case 1: case 2: case 6: _doThrow(); break; case 5: if (s[4] === "T") { return s.substring(0, 5); } break; case 4: // Must end in T, except one special case. if (first4[3] === "T" || first4 === "WITA") { return first4; } break; case 3: return first3; } _doThrow(); return ""; } function parseFracSeconds(s) { if (!isCommaOrPeriod(s[0])) { throw new Error(`must start with comma or period '${s}'`); } let n = s.length; if (n > 10) n = 10; const numS = s.substring(1, n); const value = throwInvalidNumber(numS, "fractional seconds"); if (value < 0) { throw new Error(`cannot be negative ${s}`); } let nano = value; for (let i = 0; i < 10 - n; i++) { nano *= 10; } const MILLI_TO_NANO = 1000000; const NANO_TO_MILLI = 1 / MILLI_TO_NANO; const millis = Math.round(nano * NANO_TO_MILLI); return [millis, n]; } function fracSecondsStr(code, millis) { const count = (code >> 16) & 0xfff; let padded = String(Math.floor(millis)).padStart(3, "0"); if (count < padded.length) { padded = padded.substring(0, count); } switch (65535 /* std.MaskLower */ & code) { case 33 /* std.FracSecond0 */: // trailing zeroes included. return padded.padEnd(count, "0"); case 34 /* std.FracSecond9 */: // trailing zeroes omitted return trimTrailingZeroes(padded); default: throw new Error(`invalid code for fractional seconds ${code}`); } } function trimTrailingZeroes(s) { const parts = [...s]; let lastZero = s.length - 1; while (lastZero >= 0) { if (parts[lastZero] !== "0") break; lastZero--; } return parts.slice(0, lastZero + 1).join(""); } const std0x = { "1": 3 /* std.ZeroMonth */, "2": 8 /* std.ZeroDay */, "3": 13 /* std.ZeroHour12 */, "4": 15 /* std.ZeroMinute */, "5": 17 /* std.ZeroSecond */, "6": 19 /* std.Year */, }; function charsMatch(chars, startIndex, matchS) { const matchPoints = [...matchS]; for (let i = 0; i < matchPoints.length; i++) { const c = chars[i + startIndex]; if (!c) return false; const mC = matchPoints[i]; if (mC !== c) return false; } return true; } // const layoutCache: Record = {}; // here is a native date method to get local time offset vs. UTC. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTimezoneOffset // internal function function parseLayout(layout, forTimeParse) { // const cached = layoutCache[layout]; // if (cached) return cached; // throw new GoTimeError(ErrorCode.ParseError, `bullshit layout: ${layout}`); const parts = []; const chars = [...layout]; let i = 0; while (i < chars.length) { const c = chars[i]; const nC = chars[i + 1]; switch (c) { case "J": // January, Jan if (charsMatch(chars, i, "January")) { if (isLowercase(chars[i + 7])) { break; } parts.push(0 /* std.LongMonth */); i += 7; continue; } if (charsMatch(chars, i, "Jan")) { if (isLowercase(chars[i + 3])) { break; } parts.push(1 /* std.Month */); i += 3; continue; } break; case "M": // Monday, Mon, MST if (charsMatch(chars, i, "Monday")) { parts.push(4 /* std.LongWeekDay */); i += 6; continue; } if (charsMatch(chars, i, "Mon")) { if (isLowercase(chars[i + 3])) { break; } parts.push(5 /* std.WeekDay */); i += 3; continue; } if (charsMatch(chars, i, "MST")) { parts.push(22 /* std.TZ */); i += 3; continue; } break; case "0": // 01, 02, 03, 04, 05, 06, 002 if (charsMatch(chars, i, "002")) { parts.push(10 /* std.ZeroYearDay */); i += 3; continue; } if (nC === void 0) break; const m = std0x[nC]; if (m === void 0) break; parts.push(m); i += 2; continue; case "1": // 15, 1 if (charsMatch(chars, i, "15")) { parts.push(11 /* std.Hour */); i += 2; continue; } parts.push(2 /* std.NumMonth */); i++; continue; case "2": // 2006, 2 if (charsMatch(chars, i, "2006")) { parts.push(18 /* std.LongYear */); i += 4; continue; } parts.push(6 /* std.Day */); i++; continue; case "_": // _2, _2006, __2 if (charsMatch(chars, i, "_2006")) { // _2006 is really a literal _, followed by stdLongYear parts.push("_"); parts.push(18 /* std.LongYear */); i += 5; continue; } if (charsMatch(chars, i, "__2")) { parts.push(9 /* std.UnderYearDay */); i += 3; continue; } if (charsMatch(chars, i, "_2")) { parts.push(7 /* std.UnderDay */); i += 2; continue; } break; case "3": parts.push(12 /* std.Hour12 */); i++; continue; case "4": parts.push(14 /* std.Minute */); i++; continue; case "5": parts.push(16 /* std.Second */); i++; continue; case "P": // PM if (charsMatch(chars, i, "PM")) { parts.push(20 /* std.PM */); i += 2; continue; } break; case "p": // pm if (charsMatch(chars, i, "pm")) { parts.push(21 /* std.pm */); i += 2; continue; } break; case "-": // -070000, -07:00:00, -0700, -07:00, -07 if (charsMatch(chars, i, "-070000")) { parts.push(29 /* std.NumSecondsTZ */); i += 7; continue; } if (charsMatch(chars, i, "-07:00:00")) { parts.push(32 /* std.NumColonSecondsTZ */); i += 9; continue; } if (charsMatch(chars, i, "-0700")) { parts.push(28 /* std.NumTZ */); i += 5; continue; } if (charsMatch(chars, i, "-07:00")) { parts.push(31 /* std.NumColonTZ */); i += 6; continue; } if (charsMatch(chars, i, "-07")) { parts.push(30 /* std.NumShortTZ */); i += 3; continue; } break; case "Z": // Z070000, Z07:00:00, Z0700, Z07:00, if (charsMatch(chars, i, "Z070000")) { parts.push(24 /* std.ISO8601SecondsTZ */); i += 7; continue; } if (charsMatch(chars, i, "Z07:00:00")) { parts.push(27 /* std.ISO8601ColonSecondsTZ */); i += 9; continue; } if (charsMatch(chars, i, "Z0700")) { parts.push(23 /* std.ISO8601TZ */); i += 5; continue; } if (charsMatch(chars, i, "Z07:00")) { parts.push(26 /* std.ISO8601ColonTZ */); i += 6; continue; } if (charsMatch(chars, i, "Z07")) { parts.push(25 /* std.ISO8601ShortTZ */); i += 3; continue; } break; case ".": // // ,000, or .000, or ,999, or .999 - repeated digits for fractional seconds. /* fallthrough */ case ",": if (nC === void 0) break; if (nC !== "9" && nC !== "0") break; let j = 1; while (i + j < chars.length && chars[i + j] === nC) { j++; } if (isDigit(chars[i + j])) break; // must not end with another digit. if (!forTimeParse) parts.push(c); // push the comma or whatever. let stdCode = nC === "0" ? 33 /* std.FracSecond0 */ : 34 /* std.FracSecond9 */; stdCode |= ((j - 1) & 0xfff) << 16; parts.push(stdCode); i += j; continue; default: break; } parts.push(c); i++; } return parts; } function isDigit(c) { if (c === void 0) return false; return c >= "0" && c <= "9"; } function isLowercase(c) { if (c === void 0) return false; return c >= "a" && c <= "z"; } function isCommaOrPeriod(c) { return c === "." || c === ","; } function isUppercase(c) { if (c === void 0) return false; return c >= "A" && c <= "Z"; } function isLeap(year) { // year%4 == 0 && (year%100 != 0 || year%400 == 0) // Bottom 2 bits must be clear. // For multiples of 25, bottom 4 bits must be clear. // Thanks to Cassio Neri for this trick. let mask = 0xf; if (year % 25 !== 0) { mask = 3; } return (year & mask) === 0; } function daysIn(m, year) { if (m === Month.February) { return isLeap(year) ? 29 : 28; } // 30 + ((m + (m >> 3)) & 1) return 30 + ((m + (m >> 3)) & 1); } function yearDay(year, month, day) { let yDay = 0; let m = Month.January; while (m < month) { yDay += daysIn(m, year); m++; } yDay += day; return yDay; } function isValidNumber(str) { if (str.trim() === "") return false; // reject empty or whitespace strings return !isNaN(Number(str)); } function throwInvalidNumber(str, name) { if (!isValidNumber(str)) throw new Error(`'${str}' not a valid number for ${name}`); return Number(str); } function splitDuration(ms) { const sign = ms < 0 ? -1 : 1; ms = Math.abs(ms); const hours = Math.trunc(ms / Hour); ms %= Hour; const minutes = Math.trunc(ms / Minute); ms %= Minute; const seconds = Math.trunc(ms / Second); ms %= Second; return { sign, hours, minutes, seconds, milliseconds: ms, }; } //# sourceMappingURL=Time.js.map