Category talk:Wren-date: Difference between revisions
Content added Content deleted
(→Source code: Some further changes.) |
(→Source code: Improved parsing, added time zone support.) |
||
Line 8: | Line 8: | ||
Date represents a date (and the time within that date) as the number of milliseconds |
Date represents a date (and the time within that date) as the number of milliseconds |
||
which have elapsed according to the Gregorian Proleptic calendar since midnight on |
which have elapsed according to the Gregorian Proleptic calendar since midnight on |
||
1st January, 0001. Dates before then or after the year 99,999 are not supported |
1st January, 0001. Dates before then or after the year 99,999 are not supported and leap |
||
seconds are ignored. It is also possible to store a time zone designator in a Date object even |
|||
has no direct way of detecting its locale or the current local time. |
though Wren currently has no direct way of detecting its locale or the current local time. |
||
is immutable and its 'number' property can be used as a map key. |
A Date object is immutable and its 'number' property can be used as a map key. |
||
*/ |
*/ |
||
class Date is Comparable { |
class Date is Comparable { |
||
// Private method to initialize date tables and |
// Private method to initialize date tables and other static variables. |
||
static init_() { |
static init_() { |
||
__diy = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365] |
__diy = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365] |
||
__diy2 = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366] |
__diy2 = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366] |
||
Line 30: | Line 29: | ||
"Twenty-fifth", "Twenty-sixth", "Twenty-seventh", "Twenty-eighth", |
"Twenty-fifth", "Twenty-sixth", "Twenty-seventh", "Twenty-eighth", |
||
"Twenty-ninth", "Thirtieth", "Thirty-first"] |
"Twenty-ninth", "Thirtieth", "Thirty-first"] |
||
__caps = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" |
|||
__caps12 = "ABCDEFGHIKLM" // First 12 excluding 'J' |
|||
__digs = "0123456789" |
|||
__default = standard |
__default = standard |
||
__localZone = "UTC" |
|||
// Just North America and Europe. |
|||
__tzs = { "HST": "-1000", "HDT" : "-0900", "AKST": "-0900", "AKDT": "-0800", "PST": "-0800", |
|||
"PDT": "-0700", "MST" : "-0700", "MDT" : "-0600", "CST" : "-0600", "CDT": "-0500", |
|||
"EST": "-0500", "EDT" : "-0400", "AST" : "-0400", "ADT" : "-0300", "NST": "-0330", |
|||
"NDT": "-0230", "PMST": "-0300", "PMDT": "-0200", "CVT" : "-0100", "UTC": "+0000", |
|||
"GMT": "+0000", "WET" : "+0000", "WEST": "+0100", "BST" : "+0100", "IST": "+0100", |
|||
"CET": "+0100", "CEST": "+0200", "EET" : "+0200", "EEST": "+0300", "MSK": "+0300" } |
|||
} |
} |
||
Line 40: | Line 54: | ||
// Predefined formats. |
// Predefined formats. |
||
static standard { "yyyy|-|mm|-|dd| |hh|:|MM|:|ss|.|ttt" } // readable & sortable format |
static standard { "yyyy|-|mm|-|dd| |hh|:|MM|:|ss|.|ttt" } // readable & sortable format |
||
static isoFull { "yyyy|-|mm|-|dd|T|hh|:|MM|:|ss|.|ttt|zzzzz" } // includes UTC offset |
|||
static isoDate { "yyyy|-|mm|-|dd" } |
static isoDate { "yyyy|-|mm|-|dd" } |
||
static rawDate { "yyyy |
static rawDate { "yyyy|mm|dd" } |
||
static usDate { "mm|/|dd|/|yyyy" |
static usDate { "mm|/|dd|/|yyyy" } |
||
static ukDate { "dd|/|mm|/|yyyy" |
static ukDate { "dd|/|mm|/|yyyy" } |
||
static |
static isoTime { "hh|:|MM|:|ss" } |
||
// Gets or sets the default format to be used by toString. |
// Gets or sets the default format to be used by toString. |
||
static default { __default } |
static default { __default } |
||
static default=(fmt) { __default = (fmt is List || fmt is String) ? fmt : standard } |
static default=(fmt) { __default = (fmt is List || fmt is String) ? fmt : standard } |
||
// Gets or sets the local time zone. |
|||
static localZone { __localZone } |
|||
static localZone=(tz) { __localZone = (tz = isValidTz_(tz) && tz != "J") ? tz :__localZone } |
|||
// Determines whether a particular year is a leap year. |
// Determines whether a particular year is a leap year. |
||
Line 94: | Line 113: | ||
} |
} |
||
// |
// Private helper method to check a string is all digits and convert it to an integer. |
||
static int_(str) { |
|||
for (c in str) { // make sure string only contains digits |
|||
if (!__digs.contains(c)) return null |
|||
} |
|||
return Num.fromString(str) |
|||
} |
|||
// Private helper method to get the longest integer, up to maxLen, from the start of a string. |
|||
static sint_(str, maxLen) { |
|||
if (str == "" || maxLen < 1) return null |
|||
var c = str.count |
|||
if (c < maxLen) maxLen = c |
|||
var n = null |
|||
for (i in 1..maxLen) { |
|||
var t = int_(str[0...i]) |
|||
if (t) n = t else return n |
|||
} |
|||
return n |
|||
} |
|||
// Private helper method which fills an integer with leading zeros up to a given length. |
|||
static zeroFill_(length, n) { |
|||
n = "%(n)" |
|||
return (n.count < length) ? "0" * (length - n.count) + n : n |
|||
} |
|||
// Private helper method to convert a single letter time zone designator to a UTC offset. |
|||
static militaryToUtc_(letter) { |
|||
if (letter == "J") return fmtTz_(__localZone, 4) |
|||
if (letter == "Z") return "+0000" |
|||
var ix = __caps12.indexOf(letter) |
|||
if (ix >= 0) return "-" + zeroFill_(2, ix + 1) + "00" |
|||
return "+" + zeroFill_(2, letter.bytes[0] - 77) + "00" |
|||
} |
|||
// Private helper method to convert any time zone designator to a single letter zone. |
|||
static zoneToMilitary_(tz) { |
|||
var c = tz.count |
|||
if (c <= 1) return tz |
|||
var hasName = !(tz[0] == "+" || tz[0] == "-") |
|||
var offset = (hasName) ? __tzs[tz] : tz |
|||
if (!offset) return "?" |
|||
offset = offset[0..2] |
|||
var n = Num.fromString(offset[1..2]) |
|||
if (n == 0) return "Z" |
|||
return (offset[0] == "-") ? __caps12[n-1] : __caps[12 + n] |
|||
} |
|||
// Private helper method to check if a time zone is valid or potentially valid. |
|||
// To be valid it must either be a name of between 1 and 5 consecutive capital letters |
|||
// or be a UTC offset of the form ±hh:mm, ±hhmm or just ±hh. If it's an offset it will be |
|||
// returned in ±hhmm format, otherwise as a name. If invalid, null will be returned. |
|||
static isValidTz_(tz) { |
|||
var c = tz.count |
|||
if (c == 0) return null |
|||
var hasName = !(tz[0] == "+" || tz[0] == "-") |
|||
if (hasName) { |
|||
if (c > 5) return null |
|||
for (i in 0...tz.count) { |
|||
if (!__caps.contains(tz[i])) return null |
|||
} |
|||
return tz |
|||
} |
|||
if (c < 3 || c == 4 || c > 6) return null |
|||
if (c == 3) tz = tz + "00" |
|||
if (c == 6) tz = tz.replace(":", "") |
|||
if ((c = tz.count) != 5) return null |
|||
for (i in 1...c) { |
|||
if (!__digs.contains(tz[i])) return null |
|||
} |
|||
return tz |
|||
} |
|||
// Private helper method to format a time zone. Codes (number of 'z's) are: |
|||
// 1 : Military (single capital letter) |
|||
// 2 : Abbreviated name of a zone (2 to 5 capital letters) |
|||
// 3 : Abbreviated UTC offset (±hh) |
|||
// 4 : Raw offset (±hhmm) |
|||
// 5 : Offset with colon separator (±hh:mm) |
|||
static fmtTz_(tz, code) { |
|||
if (code == 1) return (tz.count == 1) ? tz : zoneToMilitary_(tz) |
|||
var hasName = !(tz[0] == "+" || tz[0] == "-") |
|||
if (code == 2) return (hasName) ? tz : "??" |
|||
var iso = hasName ? __tzs[tz] : tz |
|||
if (!iso) iso = (tz.count == 1) ? militaryToUtc_(tz) : "+????" |
|||
if (code == 3) return iso[0..2] |
|||
if (code == 4) return iso |
|||
return iso[0..2] + ":" + iso[3..4] |
|||
} |
|||
// Private helper method to parse a UTC offset into hours and minutes. |
|||
static parseOffset_(offset) { |
|||
var sign = offset[0] |
|||
var mins = Num.fromString(offset[1..-1]) |
|||
var hrs = (mins/100).truncate |
|||
mins = mins % 100 |
|||
if (sign == "-") { |
|||
hrs = -hrs |
|||
mins = -mins |
|||
} |
|||
return [hrs, mins] |
|||
} |
|||
// Parses a 'full' IS0 8601 string into a Date object, provided that ANY single character |
|||
// separator may be used in place of dash, space, colon or dot and the following parts |
// separator may be used in place of dash, space, colon or dot and the following parts |
||
// may be omitted: the |
// may be omitted: the UTC part, the UTC and time parts, the UTC and secs/msecs part or just |
||
// Any trailing literal or unparseable text is ignored. |
// the msecs part of the time. Any trailing literal or unparseable text is ignored. |
||
static parse(str) { |
static parse(str) { |
||
str = str.trim() |
str = str.trim() |
||
var c = str.count |
var c = str.count |
||
if (c < 10) Fiber.abort("Unparseable date format string.") |
if (c < 10) Fiber.abort("Unparseable date format string.") |
||
var y = |
var y = int_(str[0..3]) |
||
var mo = |
var mo = int_(str[5..6]) |
||
var d = |
var d = int_(str[8..9]) |
||
if (c < 16) return Date.new(y, mo, d) |
if (c < 16) return Date.new(y, mo, d) |
||
var h = |
var h = int_(str[11..12]) |
||
var mi = |
var mi = int_(str[14..15]) |
||
if (c < 19) return Date.new(y, mo, d, h, mi) |
if (c < 19) return Date.new(y, mo, d, h, mi) |
||
var s = |
var s = int_(str[17..18]) |
||
if (c < 23) return Date.new(y, mo, d, h, mi, s) |
if (c < 23) return Date.new(y, mo, d, h, mi, s) |
||
var ms = |
var ms = int_(str[20..22]) |
||
return Date.new(y, mo, d, h, mi, s, ms) |
if (c < 29) return Date.new(y, mo, d, h, mi, s, ms) |
||
var tz = str[23..28] |
|||
return Date.new(y, mo, d, h, mi, s, ms, tz) |
|||
} |
|||
// Parses a date string into a Date object using the supplied format, provided that literal |
|||
// text is only matched by length (not content) and trailing literal text is ignored altogether. |
|||
// A default value is used for any missing constructor parameters. See 'format' for mask meanings. |
|||
static parse(str, fmt) { |
|||
if (fmt == Date.standard || fmt == Date.isoFull || fmt == Date.isoDate) return parse(str) |
|||
str = str.trim() |
|||
if (fmt is String) fmt = fmt.split("|") |
|||
var y = 1 |
|||
var mo = 1 |
|||
var d = 1 |
|||
var h = 0 |
|||
var mi = 0 |
|||
var s = 0 |
|||
var ms = 0 |
|||
var tz = __localZone |
|||
var used = 0 |
|||
for (i in 0...fmt.count) { |
|||
var f = fmt[i] |
|||
var c = str.count |
|||
if (f == "y") { |
|||
if (c < 1 || !(y = sint_(str, 5))) Fiber.abort("Unable to parse year.") |
|||
used = "%(y)".count |
|||
} else if (f == "yy") { |
|||
if (c < 2 || !(y = int_(str[0..1]))) Fiber.abort("Unable to parse year.") |
|||
y = y + 2000 |
|||
used = 2 |
|||
} else if (f == "yyy" || f == "yyyy" || f == "yyyyy") { |
|||
var fc = f.count |
|||
if (c < fc || !(y = int_(str[0...fc]))) Fiber.abort("Unable to parse year.") |
|||
used = fc |
|||
} else if (f == "m") { |
|||
if (c < 1 || !(mo = sint_(str, 2))) Fiber.abort("Unable to parse month.") |
|||
used = "%(mo)".count |
|||
} else if (f == "mm") { |
|||
if (c < 2 || !(mo = int_(str[0..1]))) Fiber.abort("Unable to parse month.") |
|||
used = 2 |
|||
} else if (f == "mmm") { |
|||
if (c < 3) Fiber.abort("Unable to parse abbreviated month name.") |
|||
var t = str[0..2] |
|||
var found = false |
|||
for (i in 0..11) { |
|||
if (__mths[i].startsWith(t)) { |
|||
found = true |
|||
mo = i + 1 |
|||
break |
|||
} |
|||
} |
|||
if (!found) Fiber.abort("Unable to parse abbreviated month name.") |
|||
used = 3 |
|||
} else if (f == "mmmm") { |
|||
if (c < 3) Fiber.abort("Unable to parse month name.") |
|||
var found = false |
|||
for (i in 0..11) { |
|||
if (str.startsWith(__mths[i])) { |
|||
found = true |
|||
mo = i + 1 |
|||
used = __mths[i].count |
|||
break |
|||
} |
|||
} |
|||
if (!found) Fiber.abort("Unable to parse month name.") |
|||
} else if (f == "d") { |
|||
if (c < 1 || !(d = sint_(str, 2))) Fiber.abort("Unable to parse day.") |
|||
used = "%(d)".count |
|||
} else if (f == "dd") { |
|||
if (c < 2 || !(d = int_(str[0..1]))) Fiber.abort("Unable to parse day.") |
|||
used = 2 |
|||
} else if (f == "ddd") { // cannot deduce 'day' from this but check anyway |
|||
if (c < 3) Fiber.abort("Unable to parse abbreviated day name.") |
|||
var t = str[0..2] |
|||
var found = false |
|||
for (i in 0..6) { |
|||
if (__days[i].startsWith(t)) { |
|||
found = true |
|||
break |
|||
} |
|||
} |
|||
if (!found) Fiber.abort("Unable to parse abbreviated day name.") |
|||
used = 3 |
|||
} else if (f == "dddd") { |
|||
if (c < 6) Fiber.abort("Unable to parse day name.") |
|||
var found = false |
|||
for (i in 0..6) { |
|||
if (str.startsWith(__days[i])) { |
|||
found = true |
|||
used = __days[i].count |
|||
break |
|||
} |
|||
} |
|||
if (!found) Fiber.abort("Unable to parse day name.") |
|||
} else if (f == "ooo") { // not worth checking that suffix ties in with day |
|||
if (c < 3 || !(d = sint_(str, 2))) Fiber.abort("Unable to parse day ordinal.") |
|||
used = "%(d)".count |
|||
var sfx = str[used..used+1] |
|||
if (!["th", "st", "nd", "rd"].contains(sfx)) { |
|||
Fiber.abort("Unable to parse day ordinal.") |
|||
} |
|||
} else if (f == "oooo") { |
|||
if (c < 5) Fiber.abort("Unable to parse day ordinal name.") |
|||
var found = false |
|||
for (i in 0..30) { |
|||
if (str.startsWith(__ords[i])) { |
|||
found = true |
|||
d = i + 1 |
|||
used = __ords[i].count |
|||
break |
|||
} |
|||
} |
|||
if (!found) Fiber.abort("Unable to parse day ordinal name.") |
|||
} else if (f == "h") { |
|||
if (c < 1 || !(h = sint_(str, 2))) Fiber.abort("Unable to parse hour.") |
|||
used = "%(h)".count |
|||
} else if (f == "hh") { |
|||
if (c < 2 || !(h = int_(str[0..1]))) Fiber.abort("Unable to parse hour.") |
|||
used = 2 |
|||
} else if (f == "H") { |
|||
if (c < 1 || !(h = sint_(str, 2))) Fiber.abort("Unable to parse hour.") |
|||
used = "%(h)".count |
|||
if (h == 12) h = 0 |
|||
} else if (f == "HH") { |
|||
if (c < 2 || !(h = int_(str[0..1]))) Fiber.abort("Unable to parse hour.") |
|||
if (h == 12) h = 0 |
|||
used = 2 |
|||
} else if (f == "M") { |
|||
if (c < 1 || !(mi = sint_(str, 2))) Fiber.abort("Unable to parse minute.") |
|||
used = "%(mi)".count |
|||
} else if (f == "MM") { |
|||
if (c < 2 || !(mi = int_(str[0..1]))) Fiber.abort("Unable to parse minute.") |
|||
used = 2 |
|||
} else if (f == "s") { |
|||
if (c < 1 || !(s = sint_(str, 2))) Fiber.abort("Unable to parse second.") |
|||
used = "%(s)".count |
|||
} else if (f == "ss") { |
|||
if (c < 2 || !(s = int_(str[0..1]))) Fiber.abort("Unable to parse second.") |
|||
used = 2 |
|||
} else if (f == "t") { |
|||
if (c < 1 || !(ms = sint_(str, 3))) Fiber.abort("Unable to parse millisecond.") |
|||
used = "%(ms)".count |
|||
} else if (f == "ttt") { |
|||
if (c < 3 || !(ms = int_(str[0..2]))) Fiber.abort("Unable to parse millisecond.") |
|||
used = 3 |
|||
} else if (f == "am" || f == "AM") { |
|||
if (h < 12) h = h + 12 |
|||
used = 2 |
|||
} else if (f == "z") { |
|||
if (c < 1 || !__caps.contains(str[0])) Fiber.abort("Unable to parse military time zone.") |
|||
used = 1 |
|||
} else if (f == "zz") { |
|||
if (c < 2) Fiber.abort("Unable to parse time zone name.") |
|||
tz = str[0] |
|||
var maxLen = 5 |
|||
if (c < maxLen) maxLen = c |
|||
for (i in 1...maxLen) { |
|||
if (!__caps.contains(str[i])) { |
|||
used = tz.count |
|||
break |
|||
} |
|||
tz = tz + str[i] |
|||
} |
|||
} else if (f == "zzz" || f == "zzzz") { |
|||
var fc = f.count + 2 |
|||
if (c < fc) Fiber.abort("Unable to parse time zone UTC offset.") |
|||
tz = str[0...fc] |
|||
used = fc |
|||
} else if (i == fmt.count - 1) { |
|||
break // not bothered about length of trailing literal text |
|||
} else { |
|||
f = f.replace("\f", "").replace("\v", "|") |
|||
if (c < f.count) Fiber.abort("Unable to parse literal text.") |
|||
used = f.count |
|||
} |
|||
str = str[used..-1] |
|||
} |
|||
return Date.new(y, mo, d, h, mi, s, ms, tz) |
|||
} |
} |
||
Line 120: | Line 421: | ||
y = y - 1 |
y = y - 1 |
||
return y*365 + (y/4).floor - (y/100).floor + (y/400).floor |
return y*365 + (y/4).floor - (y/100).floor + (y/400).floor |
||
} |
|||
// Private helper method which fills an integer with leading zeros up to a given length. |
|||
static zeroFill_(length, n) { |
|||
n = "%(n)" |
|||
return (n.count < length) ? "0" * (length - n.count) + n : n |
|||
} |
} |
||
Line 139: | Line 434: | ||
} |
} |
||
return "%(n)" + suffix |
return "%(n)" + suffix |
||
} |
} |
||
// Constructs a new Date object by passing it: the year, month, day, hour, minute, second |
// Constructs a new Date object by passing it: the year, month, day, hour, minute, second |
||
// and millisecond of an instance in time. It is a runtime error |
// and millisecond of an instance in time plus a time zone designator. It is a runtime error |
||
// though, for convenience, if the number of days is more than the |
// to pass invalid values though, for convenience, if the number of days is more than the |
||
// wrap around to the following month. |
// month has, they will wrap around to the following month. |
||
construct new(y, mo, d, h, mi, s, ms) { |
construct new(y, mo, d, h, mi, s, ms, tz) { |
||
if (!(y is Num && y.isInteger && y >= 0 && y <= 99999)) { |
if (!(y is Num && y.isInteger && y >= 0 && y <= 99999)) { |
||
Fiber.abort("The year must be an integer in the range [0, 99999].") |
Fiber.abort("The year must be an integer in the range [0, 99999].") |
||
Line 167: | Line 462: | ||
Fiber.abort("The millisecond must be an integer in the range [0, 999].") |
Fiber.abort("The millisecond must be an integer in the range [0, 999].") |
||
} |
} |
||
if (!(tz = Date.isValidTz_(tz))) Fiber.abort("Invalid time zone designator.") |
|||
var days = Date.soyDays_(y) |
var days = Date.soyDays_(y) |
||
days = days + d - 1 + (Date.isLeapYear(y) ? __diy2[mo-1] : __diy[mo-1]) |
days = days + d - 1 + (Date.isLeapYear(y) ? __diy2[mo-1] : __diy[mo-1]) |
||
_num = days * 86400000 + h * 3600000 + mi * 60000 + s * 1000 + ms |
_num = days * 86400000 + h * 3600000 + mi * 60000 + s * 1000 + ms |
||
_tz = tz |
|||
} |
} |
||
// |
// Private constructor for creating a Date object directly from a number of milliseconds. |
||
construct |
construct fromNum_(num, tz) { |
||
if (num < 0 || num > Date.maximum.number) Fiber.abort("Number is out of range.") |
if (num < 0 || num > Date.maximum.number) Fiber.abort("Number is out of range.") |
||
if (!(tz = Date.isValidTz_(tz))) Fiber.abort("Invalid time zone designator.") |
|||
_num = num |
_num = num |
||
_tz = tz |
|||
} |
} |
||
// Convenience methods to construct a Date object from a subset of its parameters. |
// Convenience methods to construct a Date object from a subset of its parameters. |
||
static new(y, mo, d, h, mi, s) { Date.new(y, mo, d, h, mi, s, |
static new(y, mo, d, h, mi, s, ms) { Date.new(y, mo, d, h, mi, s, ms, "UTC") } |
||
static new(y, mo, d, h, mi) { Date.new(y, mo, d, h, mi, |
static new(y, mo, d, h, mi, s) { Date.new(y, mo, d, h, mi, s, 0, "UTC") } |
||
static new(y, mo, d |
static new(y, mo, d, h, mi) { Date.new(y, mo, d, h, mi, 0, 0, "UTC") } |
||
static new(y |
static new(y, mo, d) { Date.new(y, mo, d, 0, 0, 0, 0, "UTC") } |
||
static new(y) { Date.new(y, 1, 1, 0, 0, 0, 0, "UTC") } |
|||
// Gets the component parts of this date, as a list, from its number |
// Gets the component parts of this date, as a list, from its number |
||
Line 210: | Line 510: | ||
if (mo == 0) mo = 12 |
if (mo == 0) mo = 12 |
||
var d = diff - __diy[mo-1] + 1 |
var d = diff - __diy[mo-1] + 1 |
||
return [y, mo, d, h, mi, s, ms] |
return [y, mo, d, h, mi, s, ms, _tz] |
||
} else if (Date.isLeapYear(y) && diff <= 365) { |
} else if (Date.isLeapYear(y) && diff <= 365) { |
||
var mo = 0 |
var mo = 0 |
||
Line 220: | Line 520: | ||
} |
} |
||
if (mo == 0) mo = 12 |
if (mo == 0) mo = 12 |
||
var d = diff - __diy2[mo-1] + 1 |
var d = diff - __diy2[mo-1] + 1 |
||
return [y, mo, d, h, mi, s, ms] |
return [y, mo, d, h, mi, s, ms, _tz] |
||
} |
} |
||
} |
} |
||
Line 238: | Line 538: | ||
// Return a new Date object after adding positive (or negative) increments. |
// Return a new Date object after adding positive (or negative) increments. |
||
addYears(y) { Date.new(year + y, month, day) } |
addYears(y) { Date.new(year + y, month, day, hour, minute, second, millisec, _tz) } |
||
addMonths(mo) { |
addMonths(mo) { |
||
Line 245: | Line 545: | ||
var m = month |
var m = month |
||
if (mo >= 0) { |
if (mo >= 0) { |
||
if ((m + mo) <= 12) |
if ((m + mo) <= 12) { |
||
return Date.new(year + y |
return Date.new(year + y, m + mo, day, hour, minute, second, millisec, _tz) |
||
} |
|||
return Date.new(year + y + 1, m + mo - 12, day, hour, minute, second, millisec, _tz) |
|||
} |
} |
||
if ((m + mo) >= 1) return Date.new(year + y, m + mo, day) |
if ((m + mo) >= 1) return Date.new(year + y, m + mo, day, hour, minute, second, millisec, _tz) |
||
return Date.new(year + y - 1, m + mo + 12, day) |
return Date.new(year + y - 1, m + mo + 12, day, hour, minute, second, millisec, _tz) |
||
} |
} |
||
addWeeks(w) { Date. |
addWeeks(w) { Date.fromNum_(_num + w * 86400000 * 7, _tz) } |
||
addDays(d) { Date. |
addDays(d) { Date.fromNum_(_num + d * 86400000, _tz) } |
||
addHours(h) { Date. |
addHours(h) { Date.fromNum_(_num + h * 3600000, _tz) } |
||
addMinutes(mi) { Date. |
addMinutes(mi) { Date.fromNum_(_num + mi * 60000, _tz) } |
||
addSeconds(s) { Date. |
addSeconds(s) { Date.fromNum_(_num + s * 1000, _tz) } |
||
addMillisecs(ms) { Date. |
addMillisecs(ms) { Date.fromNum_(_num + ms, _tz) } |
||
// Returns the day of the year in which this date falls. |
// Returns the day of the year in which this date falls. |
||
Line 275: | Line 577: | ||
weekOfYear { Date.isoWeek(year, month, day) } |
weekOfYear { Date.isoWeek(year, month, day) } |
||
// Returns the time zone designator for this date. |
|||
zone { _tz } |
|||
// Returns a new date object with the new time zone. Doesn't adjust the time. |
|||
changeZone(newZone) { Date.fromNum_(_num, newZone) } |
|||
// Attempts to adjust the time to a new time zone. If successful, returns a |
|||
// new Date object otherwise returns null. |
|||
adjustTime(newZone) { |
|||
if (newZone == _tz) return this // no adjustment needed |
|||
var hasName = !(_tz[0] == "+" || _tz[0] == "-") |
|||
var oldOffset |
|||
if (hasName && !(oldOffset = __tzs[_tz])) return null |
|||
if (!(newZone = Date.isValidTz_(newZone))) return null |
|||
hasName = !(newZone[0] == "+" || newZone[0] == "-") |
|||
var newOffset |
|||
if (hasName && !(newOffset = __tzs[newZone])) return null |
|||
if (oldOffset == newOffset) return Date.fromNum_(_num, newZone) // no time adjustment needed |
|||
var ohm = Date.parseOffset_(oldOffset) |
|||
var nhm = Date.parseOffset_(newOffset) |
|||
var d = Date.fromNum_(_num, newZone).addHours(nhm[0]).addMinutes(nhm[1]) |
|||
return d.addHours(-ohm[0]).addMinutes(-ohm[1]) |
|||
} |
|||
// The inherited 'clone' method just returns 'this' as Date objects are immutable. |
|||
// If you need an actual copy use this method instead. |
|||
copy() { Date.fromNum_(_num, _tz) } |
|||
// Compares this date with another one to enable comparison operators via Comparable trait. |
// Compares this date with another one to enable comparison operators via Comparable trait. |
||
compare(other) { (_num - other.number).sign } |
compare(other) { (_num - other.number).sign } |
||
Line 280: | Line 610: | ||
// Constructs the string representation of this date from a 'fmt' list or string. |
// Constructs the string representation of this date from a 'fmt' list or string. |
||
// To treat a part of the format literally (and avoid a clash with a standard mask) |
// To treat a part of the format literally (and avoid a clash with a standard mask) |
||
// insert a '\ |
// insert a '\f' within it which will otherwise be ignored. |
||
// If 'fmt' is a string then its components should be separated by '|'s. However, to use '|' |
// If 'fmt' is a string then its components should be separated by '|'s. However, to use '|' |
||
// literally, replace it with a '\ |
// literally, replace it with a '\v' which will then be changed back during processing. |
||
format(fmt) { |
format(fmt) { |
||
if (fmt is String) fmt = fmt.split("|") |
if (fmt is String) fmt = fmt.split("|") |
||
Line 342: | Line 672: | ||
str = str + Date.zeroFill_(3, parts[6]) // 3 digit millisecond |
str = str + Date.zeroFill_(3, parts[6]) // 3 digit millisecond |
||
} else if (f == "am") { |
} else if (f == "am") { |
||
str = str + (parts[3] < 12) ? "am" : "pm" |
str = str + ((parts[3] < 12) ? "am" : "pm") // am/pm designation |
||
} else if (f == "AM") { |
} else if (f == "AM") { |
||
str = str + (parts[3] < 12) ? "AM" : "PM" |
str = str + ((parts[3] < 12) ? "AM" : "PM") // AM/PM designation |
||
} else if (f == "z" || f == "zz" || f == "zzz" || |
|||
f == "zzzz" || f == "zzzzz") { // time zone designations |
|||
str = str + Date.fmtTz_(_tz, f.count) |
|||
} else { |
} else { |
||
f = f.replace("\ |
f = f.replace("\f", "").replace("\v", "|") |
||
str = str + f // literal string |
str = str + f // literal string |
||
} |
} |
||
Line 385: | Line 718: | ||
hours { _ms/3600000 } |
hours { _ms/3600000 } |
||
days { _ms/86400000 } |
days { _ms/86400000 } |
||
// The inherited 'clone' method just returns 'this' as Duration objects are immutable. |
|||
// If you need an actual copy use this method instead. |
|||
copy() { Duration.new(_ms) } |
|||
// Compares this duration with another one to enable comparison operators via Comparable trait. |
// Compares this duration with another one to enable comparison operators via Comparable trait. |