assemblyscript-temporal
Advanced tools
Comparing version 1.6.0 to 1.7.0
@@ -8,15 +8,7 @@ { | ||
{ | ||
"type": "node", | ||
"request": "launch", | ||
"name": "Launch Program", | ||
"runtimeArgs": ["-r", "ts-node/register"], | ||
"args": ["${workspaceFolder}/ts/index.ts"], | ||
"env": { "TS_NODE_PROJECT": "${workspaceFolder}/ts/tsconfig.json" } | ||
}, | ||
{ | ||
"type": "pwa-node", | ||
"request": "launch", | ||
"name": "Launch Test", | ||
"name": "Launch TZDB Generation", | ||
"skipFiles": ["<node_internals>/**"], | ||
"program": "${workspaceFolder}/test.js", | ||
"program": "${workspaceFolder}/tzdb/index.mjs", | ||
"outFiles": ["${workspaceFolder}/**/*.js"], | ||
@@ -23,0 +15,0 @@ // "runtimeExecutable": "/Users/colineberhardt/.nvm/versions/node/v10.16.3/bin/node" |
@@ -5,7 +5,7 @@ module.exports = { | ||
*/ | ||
include: ["assembly/__tests__/**/*.spec.ts"], | ||
include: ["assembly/**/__tests__/**/*.spec.ts"], | ||
/** | ||
* A set of globs passed to the glob package that quality files to be added to each test. | ||
*/ | ||
add: ["assembly/__tests__/**/*.include.ts"], | ||
add: ["assembly/**/__tests__/**/*.include.ts"], | ||
/** | ||
@@ -12,0 +12,0 @@ * All the compiler flags needed for this test suite. Make sure that a binary file is output. |
@@ -1,8 +0,8 @@ | ||
import { Date } from "../date"; | ||
import { JsDate } from "../date"; | ||
let date: Date; | ||
let date: JsDate; | ||
describe("construction from millis", () => { | ||
it("supports millis from epoch", () => { | ||
date = new Date(7677635557323226); | ||
date = new JsDate(7677635557323226); | ||
expect(date.getTime()).toBe(7677635557323226); | ||
@@ -14,7 +14,7 @@ }); | ||
it("supports year / month / day", () => { | ||
date = Date.fromString("1976-02-02"); | ||
date = JsDate.fromString("1976-02-02"); | ||
expect(date.getTime()).toBe(192067200000); | ||
date = Date.fromString("1976-2-2"); | ||
date = JsDate.fromString("1976-2-2"); | ||
expect(date.getTime()).toBe(192067200000); | ||
date = Date.fromString("2345-11-04"); | ||
date = JsDate.fromString("2345-11-04"); | ||
expect(date.getTime()).toBe(11860387200000); | ||
@@ -24,4 +24,4 @@ }); | ||
it("supports two digit years", () => { | ||
expect(Date.fromString("1976-04-02").getTime()).toBe( | ||
Date.fromString("76-04-02").getTime() | ||
expect(JsDate.fromString("1976-04-02").getTime()).toBe( | ||
JsDate.fromString("76-04-02").getTime() | ||
); | ||
@@ -31,7 +31,7 @@ }); | ||
it("supports year / month / day / hour / minute / second", () => { | ||
date = Date.fromString("1976-02-02T12:34:56"); | ||
date = JsDate.fromString("1976-02-02T12:34:56"); | ||
expect(date.getTime()).toBe(192112496000); | ||
}); | ||
it("supports milliseconds", () => { | ||
date = Date.fromString("1976-02-02T12:34:56.456"); | ||
date = JsDate.fromString("1976-02-02T12:34:56.456"); | ||
expect(date.getTime()).toBe(192112496456); | ||
@@ -43,3 +43,3 @@ }); | ||
it("from +126687-01-19T04:05:45.198Z", () => { | ||
date = new Date(3935689963545198); | ||
date = new JsDate(3935689963545198); | ||
expect(date.getUTCFullYear()).toBe(126687); | ||
@@ -55,3 +55,3 @@ expect(date.getUTCMonth()).toBe(0); | ||
it("from +148425-05-18T08:33:26.350Z", () => { | ||
date = new Date(4621685330006350); | ||
date = new JsDate(4621685330006350); | ||
expect(date.getUTCFullYear()).toBe(148425); | ||
@@ -67,3 +67,3 @@ expect(date.getUTCMonth()).toBe(4); | ||
it("from +029119-10-01T07:51:02.758Z", () => { | ||
date = new Date(856763250662758); | ||
date = new JsDate(856763250662758); | ||
expect(date.getUTCFullYear()).toBe(29119); | ||
@@ -79,3 +79,3 @@ expect(date.getUTCMonth()).toBe(9); | ||
it("from +220765-08-19T13:42:38.020Z", () => { | ||
date = new Date(6904523252558020); | ||
date = new JsDate(6904523252558020); | ||
expect(date.getUTCFullYear()).toBe(220765); | ||
@@ -91,3 +91,3 @@ expect(date.getUTCMonth()).toBe(7); | ||
it("from +264399-01-03T02:23:58.670Z", () => { | ||
date = new Date(8281459535038670); | ||
date = new JsDate(8281459535038670); | ||
expect(date.getUTCFullYear()).toBe(264399); | ||
@@ -103,3 +103,3 @@ expect(date.getUTCMonth()).toBe(0); | ||
it("from +040150-04-25T10:58:13.072Z", () => { | ||
date = new Date(1204854346693072); | ||
date = new JsDate(1204854346693072); | ||
expect(date.getUTCFullYear()).toBe(40150); | ||
@@ -115,3 +115,3 @@ expect(date.getUTCMonth()).toBe(3); | ||
it("from +147357-02-15T13:49:06.454Z", () => { | ||
date = new Date(4587974574546454); | ||
date = new JsDate(4587974574546454); | ||
expect(date.getUTCFullYear()).toBe(147357); | ||
@@ -127,3 +127,3 @@ expect(date.getUTCMonth()).toBe(1); | ||
it("from +248424-10-24T10:31:15.870Z", () => { | ||
date = new Date(7777362738675870); | ||
date = new JsDate(7777362738675870); | ||
expect(date.getUTCFullYear()).toBe(248424); | ||
@@ -139,3 +139,3 @@ expect(date.getUTCMonth()).toBe(9); | ||
it("from +014628-07-11T23:46:03.984Z", () => { | ||
date = new Date(399464523963984); | ||
date = new JsDate(399464523963984); | ||
expect(date.getUTCFullYear()).toBe(14628); | ||
@@ -151,3 +151,3 @@ expect(date.getUTCMonth()).toBe(6); | ||
it("from +039615-11-03T04:05:23.748Z", () => { | ||
date = new Date(1187987918723748); | ||
date = new JsDate(1187987918723748); | ||
expect(date.getUTCFullYear()).toBe(39615); | ||
@@ -165,3 +165,3 @@ expect(date.getUTCMonth()).toBe(10); | ||
it("setUTCSeconds", () => { | ||
date = new Date(399464523963984); | ||
date = new JsDate(399464523963984); | ||
expect(date.getUTCMilliseconds()).toBe(984); | ||
@@ -175,3 +175,3 @@ date.setUTCMilliseconds(12); | ||
it("setUTCSeconds", () => { | ||
date = new Date(372027318331986); | ||
date = new JsDate(372027318331986); | ||
expect(date.getUTCSeconds()).toBe(31); | ||
@@ -185,3 +185,3 @@ date.setUTCSeconds(12); | ||
it("setUTCMinutes", () => { | ||
date = new Date(372027318331986); | ||
date = new JsDate(372027318331986); | ||
expect(date.getUTCMinutes()).toBe(45); | ||
@@ -195,3 +195,3 @@ date.setUTCMinutes(12); | ||
it("setUTCHours", () => { | ||
date = new Date(372027318331986); | ||
date = new JsDate(372027318331986); | ||
expect(date.getUTCHours()).toBe(17); | ||
@@ -205,3 +205,3 @@ date.setUTCHours(12); | ||
it("setUTCDate", () => { | ||
date = new Date(372027318331986); | ||
date = new JsDate(372027318331986); | ||
expect(date.getUTCDate()).toBe(28); | ||
@@ -215,3 +215,3 @@ date.setUTCDate(12); | ||
it("setUTCMonth", () => { | ||
date = new Date(7899943856218720); | ||
date = new JsDate(7899943856218720); | ||
expect(date.getUTCMonth()).toBe(3); | ||
@@ -225,3 +225,3 @@ date.setUTCMonth(10); | ||
it("setUTCYear", () => { | ||
date = new Date(7941202527925698); | ||
date = new JsDate(7941202527925698); | ||
expect(date.getUTCFullYear()).toBe(253616); | ||
@@ -237,5 +237,73 @@ date.setUTCFullYear(1976); | ||
it("toISOString", () => { | ||
date = new Date(1231231231020); | ||
date = new JsDate(1231231231020); | ||
expect(date.toISOString()).toBe("2009-01-06T08:40:31.020Z"); | ||
}); | ||
}); | ||
describe("Date.UTC", () => { | ||
it("epoch", () => { | ||
expect(Date.UTC(1970)).toBe(0); | ||
}); | ||
describe("two digit years", () => { | ||
it("0", () => { | ||
expect(Date.UTC(0)).toBe(Date.UTC(1900)); | ||
}); | ||
it("99", () => { | ||
expect(Date.UTC(99)).toBe(Date.UTC(1999)); | ||
}); | ||
}); | ||
it("defaults to month=0", () => { | ||
expect(Date.UTC(2001)).toBe(Date.UTC(2001, 0)); | ||
}); | ||
it("defaults to date=1", () => { | ||
expect(Date.UTC(2001, 0)).toBe(Date.UTC(2001, 0, 1)); | ||
}); | ||
it("defaults to hours=0", () => { | ||
expect(Date.UTC(2001, 0, 1)).toBe(Date.UTC(2001, 0, 1, 0)); | ||
}); | ||
it("defaults to minutes=0", () => { | ||
expect(Date.UTC(2001, 0, 1, 0)).toBe(Date.UTC(2001, 0, 1, 0, 0)); | ||
}); | ||
it("defaults to seconds=0", () => { | ||
expect(Date.UTC(2001, 0, 1, 0, 0)).toBe(Date.UTC(2001, 0, 1, 0, 0, 0)); | ||
}); | ||
it("defaults to milliseconds=0", () => { | ||
expect(Date.UTC(2001, 0, 1, 0, 0, 0)).toBe( | ||
Date.UTC(2001, 0, 1, 0, 0, 0, 0) | ||
); | ||
}); | ||
// Taken from https://github.com/v8/v8/blob/master/test/mjsunit/date.js | ||
it("8639999999999999", () => { | ||
expect(Date.UTC(275760, 8, 12, 23, 59, 59, 999)).toBe(8639999999999999); | ||
}); | ||
it("8640000000000000", () => { | ||
expect(Date.UTC(275760, 8, 13)).toBe(8640000000000000); | ||
}); | ||
it("-8639999999999999", () => { | ||
expect(Date.UTC(-271821, 3, 20, 0, 0, 0, 1)).toBe(-8639999999999999); | ||
}); | ||
it("-8640000000000000", () => { | ||
expect(Date.UTC(-271821, 3, 20)).toBe(-8640000000000000); | ||
}); | ||
it("obscure date values", () => { | ||
expect(Date.UTC(1970, 0, 1 + 100000001, -24)).toBe(8640000000000000); | ||
}); | ||
it("obscure date values", () => { | ||
expect(Date.UTC(1970, 0, 1 - 100000001, 24)).toBe(-8640000000000000); | ||
}); | ||
}); |
@@ -0,6 +1,23 @@ | ||
// @ts-ignore | ||
@lazy | ||
export const MILLIS_PER_DAY = 1_000 * 60 * 60 * 24; | ||
// @ts-ignore | ||
@lazy | ||
export const MILLIS_PER_HOUR = 1_000 * 60 * 60; | ||
// @ts-ignore | ||
@lazy | ||
export const MILLIS_PER_MINUTE = 1_000 * 60; | ||
// @ts-ignore | ||
@lazy | ||
export const MILLIS_PER_SECOND = 1_000; | ||
// @ts-ignore | ||
@lazy | ||
export const MICROS_PER_SECOND = 1_000_000; | ||
// @ts-ignore | ||
@lazy | ||
export const NANOS_PER_SECOND = 1_000_000_000; |
// TODO: This functionality will likely be moved into the AssemblyScript Standard Library | ||
// https://github.com/AssemblyScript/assemblyscript/pull/1768 | ||
import { YMD } from "./utils"; | ||
import { MILLIS_PER_DAY, MILLIS_PER_HOUR, MILLIS_PER_MINUTE, MILLIS_PER_SECOND } from "./constants"; | ||
import { checkRange, YMD } from "./utils"; | ||
import { | ||
MILLIS_PER_DAY, | ||
MILLIS_PER_HOUR, | ||
MILLIS_PER_MINUTE, | ||
MILLIS_PER_SECOND | ||
} from "./constants"; | ||
export class Date { | ||
export class JsDate { | ||
epochMilliseconds: i64; | ||
@@ -14,3 +19,3 @@ | ||
static fromString(dateTimeString: string): Date { | ||
static fromString(dateTimeString: string): JsDate { | ||
let hour: i32 = 0, | ||
@@ -46,3 +51,3 @@ minute: i32 = 0, | ||
return new Date( | ||
return new JsDate( | ||
i64(days_from_civil(year, month, day)) * MILLIS_PER_DAY + | ||
@@ -56,2 +61,24 @@ hour * MILLIS_PER_HOUR + | ||
// https://tc39.es/ecma262/#sec-date.utc | ||
static UTC( | ||
year: i32, | ||
month: i32 = 0, | ||
date: i32 = 1, | ||
hours: i32 = 0, | ||
minutes: i32 = 0, | ||
seconds: i32 = 0, | ||
ms: i32 = 0 | ||
): i64 { | ||
if (checkRange(year, 0, 99)) { | ||
year += 1900; | ||
} | ||
return ( | ||
<i64>days_from_civil(year, month + 1, date) * MILLIS_PER_DAY + | ||
hours * MILLIS_PER_HOUR + | ||
minutes * MILLIS_PER_MINUTE + | ||
seconds * MILLIS_PER_SECOND + | ||
ms | ||
); | ||
} | ||
getTime(): i64 { | ||
@@ -58,0 +85,0 @@ return this.epochMilliseconds; |
@@ -9,1 +9,3 @@ import "./env"; | ||
export * from "./now"; | ||
export * from "./timezone"; | ||
export * from "./zoneddatetime"; |
import { PlainDateTime } from "./plaindatetime"; | ||
import { Date as JsDate } from "./date"; | ||
import { JsDate } from "./date"; | ||
import { PlainDate } from "./plaindate"; | ||
@@ -4,0 +4,0 @@ |
@@ -12,7 +12,14 @@ // for the proposal-temporal implementation, most of the business logic | ||
import { MILLIS_PER_SECOND, NANOS_PER_SECOND } from "./constants"; | ||
import { log } from "./env"; | ||
import { JsDate } from "./date"; | ||
// @ts-ignore | ||
@lazy | ||
const YEAR_MIN = -271821; | ||
// @ts-ignore | ||
@lazy | ||
const YEAR_MAX = 275760; | ||
// @ts-ignore | ||
@lazy | ||
let __null = false; | ||
@@ -75,2 +82,6 @@ | ||
export function floorDivI64(a: i64, b: i64): i64 { | ||
return (a >= 0 ? a : a - b + 1) / b; | ||
} | ||
// @ts-ignore: decorator | ||
@@ -132,2 +143,3 @@ @inline | ||
// https://github.com/tc39/proposal-temporal/blob/49629f785eee61e9f6641452e01e995f846da3a1/polyfill/lib/ecmascript.mjs#L2171 | ||
// returns day of week in range [1,7], where 7 = Sunday | ||
export function dayOfWeek(year: i32, month: i32, day: i32): i32 { | ||
@@ -194,2 +206,25 @@ const tab = memory.data<u8>([0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4]); | ||
export function balanceDateTime(year: i32, month: i32, day: i32, hour: i32, | ||
minute: i32, | ||
second: i32, | ||
millisecond: i32, | ||
microsecond: i32, | ||
nanosecond: i64): DT { | ||
const balancedTime = balanceTime(hour, minute, second, millisecond, microsecond, nanosecond); | ||
const balancedDate = balanceDate(year, month, day + balancedTime.deltaDays); | ||
return { | ||
year: balancedDate.year, | ||
month: balancedDate.month, | ||
day: balancedDate.day, | ||
hour: balancedTime.hour, | ||
minute: balancedTime.minute, | ||
second: balancedTime.second, | ||
millisecond: balancedTime.millisecond, | ||
microsecond: balancedTime.microsecond, | ||
nanosecond: balancedTime.nanosecond | ||
}; | ||
} | ||
// @ts-ignore: decorator | ||
@@ -624,3 +659,3 @@ @inline | ||
mid = addDate(yr1, mo1, d1, years, months, 0, 0, Overflow.Constrain); | ||
midSign = compareTemporalDate(mid.year, mid.month, mid.day, yr2, mo2, d2); | ||
midSign = -compareTemporalDate(mid.year, mid.month, mid.day, yr2, mo2, d2); | ||
@@ -644,6 +679,5 @@ if (midSign === 0) { | ||
mid = addDate(yr1, mo1, d1, years, months, 0, 0, Overflow.Constrain); | ||
midSign = compareTemporalDate(yr1, mo1, d1, mid.year, mid.month, mid.day); | ||
} | ||
let days = 0; // If we get here, months and years are correct (no overflow), and `mid` | ||
let days = endDay - mid.day; // If we get here, months and years are correct (no overflow), and `mid` | ||
// is within the range from `start` to `end`. To count the days between | ||
@@ -657,11 +691,10 @@ // `mid` and `end`, there are 3 cases: | ||
// 1) same month: use simple subtraction | ||
days = endDay - mid.day; | ||
} else if (sign < 0) { | ||
// 2) end is previous month from intermediate (negative duration) | ||
// Example: intermediate: Feb 1, end: Jan 30, DaysInMonth = 31, days = -2 | ||
days = endDay - daysInMonth(endYear, endMonth) - mid.day; | ||
days -= daysInMonth(endYear, endMonth); | ||
} else { | ||
// 3) end is next month from intermediate (positive duration) | ||
// Example: intermediate: Jan 29, end: Feb 1, DaysInMonth = 31, days = 3 | ||
days = endDay + daysInMonth(mid.year, mid.month) - mid.day; | ||
days += daysInMonth(mid.year, mid.month); | ||
} | ||
@@ -741,11 +774,10 @@ | ||
); | ||
hours *= sign; | ||
minutes *= sign; | ||
seconds *= sign; | ||
milliseconds *= sign; | ||
microseconds *= sign; | ||
nanoseconds *= sign; | ||
let balancedTime = balanceTime( | ||
hours, minutes, seconds, milliseconds, microseconds, nanoseconds | ||
const balancedTime = balanceTime( | ||
hours * sign, | ||
minutes * sign, | ||
seconds * sign, | ||
milliseconds * sign, | ||
microseconds * sign, | ||
nanoseconds * sign | ||
); | ||
@@ -764,4 +796,3 @@ | ||
balancedTime.nanosecond * sign | ||
) | ||
); | ||
} | ||
@@ -826,4 +857,2 @@ | ||
): DT { | ||
// Add the time part | ||
let deltaDays = 0; | ||
const addedTime = addTime( | ||
@@ -834,3 +863,2 @@ hour, minute, second, millisecond, microsecond, nanosecond, | ||
deltaDays = addedTime.deltaDays; | ||
hour = addedTime.hour; | ||
@@ -842,3 +870,3 @@ minute = addedTime.minute; | ||
nanosecond = addedTime.nanosecond; | ||
days += deltaDays; // Delegate the date part addition to the calendar | ||
days += addedTime.deltaDays; // Delegate the date part addition to the calendar | ||
@@ -940,44 +968,68 @@ const addedDate = addDate(year, month, day, years, months, weeks, days,overflow); | ||
function balanceTime( | ||
hour: i32, | ||
minute: i32, | ||
second: i32, | ||
millisecond: i32, | ||
microsecond: i32, | ||
nanosecond: i32 | ||
hour: i64, | ||
minute: i64, | ||
second: i64, | ||
millisecond: i64, | ||
microsecond: i64, | ||
nanosecond: i64 | ||
): BalancedTime { | ||
let quotient = floorDiv(nanosecond, 1000); | ||
let quotient = floorDivI64(nanosecond, 1000); | ||
microsecond += quotient; | ||
nanosecond -= quotient * 1000; | ||
quotient = floorDiv(microsecond, 1000); | ||
quotient = floorDivI64(microsecond, 1000); | ||
millisecond += quotient; | ||
microsecond -= quotient * 1000; | ||
quotient = floorDiv(millisecond, 1000); | ||
quotient = floorDivI64(millisecond, 1000); | ||
second += quotient; | ||
millisecond -= quotient * 1000; | ||
quotient = floorDiv(second, 60); | ||
quotient = floorDivI64(second, 60); | ||
minute += quotient; | ||
second -= quotient * 60; | ||
quotient = floorDiv(minute, 60); | ||
quotient = floorDivI64(minute, 60); | ||
hour += quotient; | ||
minute -= quotient * 60; | ||
let deltaDays = floorDiv(hour, 24); | ||
let deltaDays = floorDivI64(hour, 24); | ||
hour -= deltaDays * 24; | ||
return { | ||
deltaDays, | ||
hour, | ||
minute, | ||
second, | ||
millisecond, | ||
microsecond, | ||
nanosecond | ||
deltaDays: i32(deltaDays), | ||
hour : i32(hour), | ||
minute : i32(minute), | ||
second: i32(second), | ||
millisecond : i32(millisecond), | ||
microsecond: i32(microsecond), | ||
nanosecond: i32(nanosecond) | ||
}; | ||
} | ||
export function getPartsFromEpoch(epochNanoseconds: i64): DT { | ||
const quotient = epochNanoseconds / 1_000_000; | ||
const remainder = epochNanoseconds % 1_000_000; | ||
let epochMilliseconds = +quotient; | ||
let nanos = +remainder; | ||
if (nanos < 0) { | ||
nanos += 1_000_000; | ||
epochMilliseconds -= 1; | ||
} | ||
const microsecond = i32((nanos / 1_000) % 1_000); | ||
const nanosecond = i32(nanos % 1_000); | ||
const item = new JsDate(epochMilliseconds); | ||
const year = item.getUTCFullYear(); | ||
const month = item.getUTCMonth() + 1; | ||
const day = item.getUTCDate(); | ||
const hour = item.getUTCHours(); | ||
const minute = item.getUTCMinutes(); | ||
const second = item.getUTCSeconds(); | ||
const millisecond = item.getUTCMilliseconds(); | ||
return { year, month, day, hour, minute, second, millisecond, microsecond, nanosecond }; | ||
} | ||
// @ts-ignore: decorator | ||
@@ -1006,1 +1058,9 @@ @inline | ||
} | ||
export function formatTimeZoneOffsetString(offsetNanoseconds: i64): string { | ||
const sign = offsetNanoseconds < 0 ? '-' : '+'; | ||
offsetNanoseconds = abs(offsetNanoseconds); | ||
const balanced = balanceTime(0, 0, 0, 0, 0, offsetNanoseconds); | ||
return sign + toPaddedString(balanced.hour) + ":" + | ||
toPaddedString(balanced.minute); | ||
} |
{ | ||
"name": "assemblyscript-temporal", | ||
"version": "1.6.0", | ||
"version": "1.7.0", | ||
"description": "An implementation of temporal within AssemblyScript, with an initial focus on non-timezone-aware classes and functionality.", | ||
"main": "index.js", | ||
"ascMain": "assembly/index.ts", | ||
"types": "assembly/index.ts", | ||
"scripts": { | ||
@@ -19,3 +20,4 @@ "test": "asp --verbose --nologo", | ||
"@assemblyscript/loader": "^0.18.20", | ||
"assemblyscript": "^0.18.20" | ||
"assemblyscript": "^0.18.20", | ||
"prettier": "^2.2.1" | ||
}, | ||
@@ -22,0 +24,0 @@ "dependencies": { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
2458862
78
24173
4
1
80
2
1