|
1 |
| -import { |
2 |
| - UTC as Date_UTC, |
3 |
| - now as Date_now |
4 |
| -} from "./bindings/Date"; |
| 1 | +import { E_VALUEOUTOFRANGE } from "util/error"; |
| 2 | +import { now as Date_now } from "./bindings/Date"; |
5 | 3 |
|
6 | 4 | export class Date {
|
7 |
| - |
8 | 5 | @inline static UTC(
|
9 | 6 | year: i32,
|
10 | 7 | month: i32 = 0,
|
11 | 8 | day: i32 = 1,
|
12 | 9 | hour: i32 = 0,
|
13 | 10 | minute: i32 = 0,
|
14 | 11 | second: i32 = 0,
|
15 |
| - millisecond: i64 = 0 |
| 12 | + millisecond: i32 = 0 |
16 | 13 | ): i64 {
|
17 |
| - return <i64>Date_UTC(year, month, day, hour, minute, second, <f64>millisecond); |
| 14 | + return epochMillis(year, month + 1, day, hour, minute, second, millisecond); |
18 | 15 | }
|
19 | 16 |
|
20 | 17 | @inline static now(): i64 {
|
21 | 18 | return <i64>Date_now();
|
22 | 19 | }
|
23 | 20 |
|
24 |
| - private value: i64; |
| 21 | + static fromString(dateTimeString: string): Date { |
| 22 | + let hour: i32 = 0, |
| 23 | + minute: i32 = 0, |
| 24 | + second: i32 = 0, |
| 25 | + millisecond: i32 = 0; |
| 26 | + let dateString: string; |
| 27 | + |
| 28 | + if (dateTimeString.includes("T")) { |
| 29 | + // includes a time component |
| 30 | + const parts = dateTimeString.split("T"); |
| 31 | + const timeString = parts[1]; |
| 32 | + // parse the HH-MM-SS component |
| 33 | + const timeParts = timeString.split(":"); |
| 34 | + hour = I32.parseInt(timeParts[0]); |
| 35 | + minute = I32.parseInt(timeParts[1]); |
| 36 | + if (timeParts[2].includes(".")) { |
| 37 | + // includes milliseconds |
| 38 | + const secondParts = timeParts[2].split("."); |
| 39 | + second = I32.parseInt(secondParts[0]); |
| 40 | + millisecond = I32.parseInt(secondParts[1]); |
| 41 | + } else { |
| 42 | + second = I32.parseInt(timeParts[2]); |
| 43 | + } |
| 44 | + dateString = parts[0]; |
| 45 | + } else { |
| 46 | + dateString = dateTimeString; |
| 47 | + } |
| 48 | + // parse the YYYY-MM-DD component |
| 49 | + const parts = dateString.split("-"); |
| 50 | + const year = I32.parseInt( |
| 51 | + parts[0].length == 2 ? "19" + parts[0] : parts[0] |
| 52 | + ); |
| 53 | + const month = I32.parseInt(parts[1]); |
| 54 | + const day = I32.parseInt(parts[2]); |
25 | 55 |
|
26 |
| - constructor(value: i64) { |
27 |
| - this.value = value; |
| 56 | + return new Date( |
| 57 | + epochMillis(year, month, day, hour, minute, second, millisecond) |
| 58 | + ); |
| 59 | + } |
| 60 | + |
| 61 | + private epochMillis: i64; |
| 62 | + |
| 63 | + constructor(epochMillis: i64) { |
| 64 | + this.epochMillis = epochMillis; |
28 | 65 | }
|
29 | 66 |
|
30 | 67 | getTime(): i64 {
|
31 |
| - return this.value; |
| 68 | + return this.epochMillis; |
32 | 69 | }
|
33 | 70 |
|
34 | 71 | setTime(value: i64): i64 {
|
35 |
| - this.value = value; |
| 72 | + this.epochMillis = value; |
36 | 73 | return value;
|
37 | 74 | }
|
| 75 | + |
| 76 | + getUTCFullYear(): i32 { |
| 77 | + ymdFromEpochDays(i32(this.epochMillis / MILLIS_PER_DAY)); |
| 78 | + return year; |
| 79 | + } |
| 80 | + |
| 81 | + getUTCMonth(): i32 { |
| 82 | + ymdFromEpochDays(i32(this.epochMillis / MILLIS_PER_DAY)); |
| 83 | + return month - 1; |
| 84 | + } |
| 85 | + |
| 86 | + getUTCDate(): i32 { |
| 87 | + ymdFromEpochDays(i32(this.epochMillis / MILLIS_PER_DAY)); |
| 88 | + return day; |
| 89 | + } |
| 90 | + |
| 91 | + getUTCHours(): i32 { |
| 92 | + return i32(this.epochMillis % MILLIS_PER_DAY) / MILLIS_PER_HOUR; |
| 93 | + } |
| 94 | + |
| 95 | + getUTCMinutes(): i32 { |
| 96 | + return i32(this.epochMillis % MILLIS_PER_HOUR) / MILLIS_PER_MINUTE; |
| 97 | + } |
| 98 | + |
| 99 | + getUTCSeconds(): i32 { |
| 100 | + return i32(this.epochMillis % MILLIS_PER_MINUTE) / MILLIS_PER_SECOND; |
| 101 | + } |
| 102 | + |
| 103 | + getUTCMilliseconds(): i32 { |
| 104 | + return i32(this.epochMillis % MILLIS_PER_SECOND); |
| 105 | + } |
| 106 | + |
| 107 | + setUTCMilliseconds(value: i32): void { |
| 108 | + this.epochMillis += value - this.getUTCMilliseconds(); |
| 109 | + } |
| 110 | + |
| 111 | + setUTCSeconds(value: i32): void { |
| 112 | + throwIfNotInRange(value, 0, 59); |
| 113 | + this.epochMillis += (value - this.getUTCSeconds()) * MILLIS_PER_SECOND; |
| 114 | + } |
| 115 | + |
| 116 | + setUTCMinutes(value: i32): void { |
| 117 | + throwIfNotInRange(value, 0, 59); |
| 118 | + this.epochMillis += (value - this.getUTCMinutes()) * MILLIS_PER_MINUTE; |
| 119 | + } |
| 120 | + |
| 121 | + setUTCHours(value: i32): void { |
| 122 | + throwIfNotInRange(value, 0, 23); |
| 123 | + this.epochMillis += (value - this.getUTCHours()) * MILLIS_PER_HOUR; |
| 124 | + } |
| 125 | + |
| 126 | + setUTCDate(value: i32): void { |
| 127 | + ymdFromEpochDays(i32(this.epochMillis / MILLIS_PER_DAY)); |
| 128 | + throwIfNotInRange(value, 1, daysInMonth(year, month)); |
| 129 | + const mills = this.epochMillis % MILLIS_PER_DAY; |
| 130 | + this.epochMillis = |
| 131 | + i64(daysSinceEpoch(year, month, value)) * MILLIS_PER_DAY + mills; |
| 132 | + } |
| 133 | + |
| 134 | + setUTCMonth(value: i32): void { |
| 135 | + throwIfNotInRange(value, 1, 12); |
| 136 | + ymdFromEpochDays(i32(this.epochMillis / MILLIS_PER_DAY)); |
| 137 | + const mills = this.epochMillis % MILLIS_PER_DAY; |
| 138 | + this.epochMillis = |
| 139 | + i64(daysSinceEpoch(year, value + 1, day)) * MILLIS_PER_DAY + mills; |
| 140 | + } |
| 141 | + |
| 142 | + setUTCFullYear(value: i32): void { |
| 143 | + ymdFromEpochDays(i32(this.epochMillis / MILLIS_PER_DAY)); |
| 144 | + const mills = this.epochMillis % MILLIS_PER_DAY; |
| 145 | + this.epochMillis = |
| 146 | + i64(daysSinceEpoch(value, month, day)) * MILLIS_PER_DAY + mills; |
| 147 | + } |
| 148 | + |
| 149 | + toISOString(): string { |
| 150 | + ymdFromEpochDays(i32(this.epochMillis / MILLIS_PER_DAY)); |
| 151 | + |
| 152 | + let yearStr = year.toString(); |
| 153 | + if (yearStr.length > 4) { |
| 154 | + yearStr = "+" + yearStr.padStart(6, "0"); |
| 155 | + } |
| 156 | + |
| 157 | + return ( |
| 158 | + yearStr + |
| 159 | + "-" + |
| 160 | + month.toString().padStart(2, "0") + |
| 161 | + "-" + |
| 162 | + day.toString().padStart(2, "0") + |
| 163 | + "T" + |
| 164 | + this.getUTCHours().toString().padStart(2, "0") + |
| 165 | + ":" + |
| 166 | + this.getUTCMinutes().toString().padStart(2, "0") + |
| 167 | + ":" + |
| 168 | + this.getUTCSeconds().toString().padStart(2, "0") + |
| 169 | + "." + |
| 170 | + this.getUTCMilliseconds().toString().padStart(3, "0") + |
| 171 | + "Z" |
| 172 | + ); |
| 173 | + } |
| 174 | +} |
| 175 | + |
| 176 | +function epochMillis( |
| 177 | + year: i32, |
| 178 | + month: i32, |
| 179 | + day: i32, |
| 180 | + hour: i32, |
| 181 | + minute: i32, |
| 182 | + second: i32, |
| 183 | + milliseconds: i32 |
| 184 | +): i64 { |
| 185 | + return ( |
| 186 | + i64(daysSinceEpoch(year, month, day)) * MILLIS_PER_DAY + |
| 187 | + hour * MILLIS_PER_HOUR + |
| 188 | + minute * MILLIS_PER_MINUTE + |
| 189 | + second * MILLIS_PER_SECOND + |
| 190 | + milliseconds |
| 191 | + ); |
| 192 | +} |
| 193 | + |
| 194 | +function throwIfNotInRange(value: i32, lower: i32, upper: i32): void { |
| 195 | + if (value < lower || value > upper) throw new RangeError(E_VALUEOUTOFRANGE); |
| 196 | +} |
| 197 | + |
| 198 | +const MILLIS_PER_DAY = 1_000 * 60 * 60 * 24; |
| 199 | +const MILLIS_PER_HOUR = 1_000 * 60 * 60; |
| 200 | +const MILLIS_PER_MINUTE = 1_000 * 60; |
| 201 | +const MILLIS_PER_SECOND = 1_000; |
| 202 | + |
| 203 | +// http://howardhinnant.github.io/date_algorithms.html#is_leap |
| 204 | +function isLeap(y: i32): bool { |
| 205 | + return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0); |
| 206 | +} |
| 207 | + |
| 208 | +function daysInMonth(year: i32, month: i32): i32 { |
| 209 | + return month == 2 |
| 210 | + ? 28 + i32(isLeap(year)) |
| 211 | + : 30 + ((month + i32(month >= 8)) & 1); |
| 212 | +} |
| 213 | + |
| 214 | +// ymdFromEpochDays returns values via globals to avoid allocations |
| 215 | +let year: i32, month: i32, day: i32; |
| 216 | +// see: http://howardhinnant.github.io/date_algorithms.html#civil_from_days |
| 217 | +function ymdFromEpochDays(z: i32): void { |
| 218 | + z += 719468; |
| 219 | + const era = (z >= 0 ? z : z - 146096) / 146097; |
| 220 | + const doe = z - era * 146097; // [0, 146096] |
| 221 | + const yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; // [0, 399] |
| 222 | + year = yoe + era * 400; |
| 223 | + const doy = doe - (365 * yoe + yoe / 4 - yoe / 100); // [0, 365] |
| 224 | + const mp = (5 * doy + 2) / 153; // [0, 11] |
| 225 | + day = doy - (153 * mp + 2) / 5 + 1; // [1, 31] |
| 226 | + month = mp + (mp < 10 ? 3 : -9); // [1, 12] |
| 227 | + year += (month <= 2 ? 1 : 0); |
| 228 | +} |
| 229 | + |
| 230 | +// http://howardhinnant.github.io/date_algorithms.html#days_from_civil |
| 231 | +function daysSinceEpoch(y: i32, m: i32, d: i32): i32 { |
| 232 | + y -= m <= 2 ? 1 : 0; |
| 233 | + const era = (y >= 0 ? y : y - 399) / 400; |
| 234 | + const yoe = y - era * 400; // [0, 399] |
| 235 | + const doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; // [0, 365] |
| 236 | + const doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; // [0, 146096] |
| 237 | + return era * 146097 + doe - 719468; |
38 | 238 | }
|
0 commit comments