[IMP] pyjs: add date/datetime utilities

Introduce utilities to calculate the start / end of a period given a date or datetime object in the JS python interpreter.

This commit is the JS implementation of commit 960360afe4
This commit is contained in:
Adrian Torres
2018-08-09 15:00:37 +02:00
committed by GitHub
parent 3cebe88d8d
commit a1bad2191c
2 changed files with 287 additions and 6 deletions
+200 -6
View File
@@ -112,6 +112,25 @@ function ymd2ord(year, month, day) {
day;
}
function get_quarter_number(month) {
return Math.ceil(month / 3);
}
function get_quarter(year, month) {
var quarter_number = get_quarter_number(month);
var month_from = ((quarter_number - 1) * 3) + 1
var date_from = {year: year, month: month_from, day: 1}
var date_to = {year: year, month: month_from + 2, day: days_in_month(year, month)}
return [date_from, date_to];
}
function get_day_of_week(year, month, day) {
// Since JavaScript is a piece of garbage, months start at 0
var d = new Date(year, month - 1, day);
// Convert to ISO8601: Monday = 0 ... Sunday = 6
return (d.getDay() + 6) % 7;
}
var DI400Y = days_before_year(401);
var DI100Y = days_before_year(101);
var DI4Y = days_before_year(5);
@@ -413,6 +432,17 @@ datetime.datetime = py.type('datetime', null, {
this[key] = asJS(args[key]);
}
},
__eq__: function (other) {
return (this.year === other.year
&& this.month === other.month
&& this.day === other.day
&& this.hour === other.hour
&& this.minute === other.minute
&& this.second === other.second
&& this.microsecond === other.microsecond
&& this.tzinfo === other.tzinfo)
? py.True : py.False;
},
replace: function () {
var args = py.PY_parseArgs(arguments, [
['year', py.None], ['month', py.None], ['day', py.None],
@@ -428,6 +458,87 @@ datetime.datetime = py.type('datetime', null, {
}
return py.PY_call(datetime.datetime, params);
},
start_of: function() {
var args = py.PY_parseArgs(arguments, 'granularity');
var granularity = args.granularity.toJSON();
if (granularity === 'year') {
return py.PY_call(datetime.datetime, [this.year, 1, 1]);
} else if (granularity === 'quarter') {
var quarter = get_quarter(this.year, this.month)[0];
return py.PY_call(datetime.datetime, [quarter.year, quarter.month, quarter.day]);
} else if (granularity === 'month') {
return py.PY_call(datetime.datetime, [this.year, this.month, 1]);
} else if (granularity === 'week') {
var dow = get_day_of_week(this.year, this.month, this.day);
return py.PY_call(datetime.datetime, [this.year, this.month, this.day - dow]);
} else if (granularity === 'day') {
return py.PY_call(datetime.datetime, [this.year, this.month, this.day]);
} else if (granularity === 'hour') {
return py.PY_call(datetime.datetime, [this.year, this.month, this.day, this.hour]);
} else {
throw new Error(
'ValueError: ' + granularity + ' is not a supported granularity, supported ' +
' granularities are: year, quarter, month, week, day and hour.'
)
}
},
end_of: function () {
var args = py.PY_parseArgs(arguments, 'granularity');
var granularity = args.granularity.toJSON();
var min = [23, 59, 59];
if (granularity === 'year') {
return py.PY_call(datetime.datetime, [this.year, 12, 31].concat(min));
} else if (granularity === 'quarter') {
var quarter = get_quarter(this.year, this.month)[1];
return py.PY_call(
datetime.datetime, [quarter.year, quarter.month, quarter.day].concat(min)
);
} else if (granularity === 'month') {
var dom = days_in_month(this.year, this.month);
return py.PY_call(datetime.datetime, [this.year, this.month, dom].concat(min));
} else if (granularity === 'week') {
var dow = get_day_of_week(this.year, this.month, this.day);
return py.PY_call(
datetime.datetime, [this.year, this.month, this.day + (6 - dow)].concat(min)
);
} else if (granularity === 'day') {
return py.PY_call(datetime.datetime, [this.year, this.month, this.day].concat(min));
} else if (granularity === 'hour') {
return py.PY_call(
datetime.datetime, [this.year, this.month, this.day, this.hour, 59, 59]
);
} else {
throw new Error(
'ValueError: ' + granularity + ' is not a supported granularity, supported ' +
' granularities are: year, quarter, month, week, day and hour.'
)
}
},
add: function() {
var args = py.PY_parseArgs(arguments, [
['years', py.None], ['months', py.None], ['days', py.None],
['hours', py.None], ['minutes', py.None], ['seconds', py.None],
]);
return py.PY_add(this, py.PY_call(relativedelta, {
'years': args.years,
'months': args.months,
'days': args.days,
'hours': args.hours,
'minutes': args.minutes,
'seconds': args.seconds,
}));
},
subtract: function() {
var args = py.PY_parseArgs(arguments, [
['years', py.None], ['months', py.None], ['days', py.None],
['hours', py.None], ['minutes', py.None], ['seconds', py.None],
]);
var params = {};
for (var key in args) {
params[key] = (args[key] === py.None ? args[key] : py.float.fromJSON(-asJS(args[key])));
}
return py.PY_add(this, py.PY_call(relativedelta, params));
},
strftime: function () {
var self = this;
var args = py.PY_parseArgs(arguments, 'format');
@@ -549,6 +660,71 @@ datetime.date = py.type('date', null, {
}
return py.PY_call(datetime.date, params);
},
start_of: function() {
var args = py.PY_parseArgs(arguments, 'granularity');
var granularity = args.granularity.toJSON();
if (granularity === 'year') {
return py.PY_call(datetime.date, [this.year, 1, 1]);
} else if (granularity === 'quarter') {
var quarter = get_quarter(this.year, this.month)[0];
return py.PY_call(datetime.date, [quarter.year, quarter.month, quarter.day]);
} else if (granularity === 'month') {
return py.PY_call(datetime.date, [this.year, this.month, 1]);
} else if (granularity === 'week') {
var dow = get_day_of_week(this.year, this.month, this.day);
return py.PY_call(datetime.date, [this.year, this.month, this.day - dow]);
} else if (granularity === 'day') {
return py.PY_call(datetime.date, [this.year, this.month, this.day]);
} else {
throw new Error(
'ValueError: ' + granularity + ' is not a supported granularity, supported ' +
' granularities are: year, quarter, month, week and day.'
)
}
},
end_of: function () {
var args = py.PY_parseArgs(arguments, 'granularity');
var granularity = args.granularity.toJSON();
if (granularity === 'year') {
return py.PY_call(datetime.date, [this.year, 12, 31]);
} else if (granularity === 'quarter') {
var quarter = get_quarter(this.year, this.month)[1];
return py.PY_call(datetime.date, [quarter.year, quarter.month, quarter.day]);
} else if (granularity === 'month') {
var dom = days_in_month(this.year, this.month);
return py.PY_call(datetime.date, [this.year, this.month, dom]);
} else if (granularity === 'week') {
var dow = get_day_of_week(this.year, this.month, this.day);
return py.PY_call(datetime.date, [this.year, this.month, this.day + (6 - dow)]);
} else if (granularity === 'day') {
return py.PY_call(datetime.date, [this.year, this.month, this.day]);
} else {
throw new Error(
'ValueError: ' + granularity + ' is not a supported granularity, supported ' +
' granularities are: year, quarter, month, week and day.'
)
}
},
add: function() {
var args = py.PY_parseArgs(arguments, [
['years', py.None], ['months', py.None], ['days', py.None],
]);
return py.PY_add(this, py.PY_call(relativedelta, {
'years': args.years,
'months': args.months,
'days': args.days,
}));
},
subtract: function() {
var args = py.PY_parseArgs(arguments, [
['years', py.None], ['months', py.None], ['days', py.None],
]);
var params = {};
for (var key in args) {
params[key] = (args[key] === py.None ? args[key] : py.float.fromJSON(-asJS(args[key])));
}
return py.PY_add(this, py.PY_call(relativedelta, params));
},
__add__: function (other) {
if (!py.PY_isInstance(other, datetime.timedelta)) {
return py.NotImplemented;
@@ -610,13 +786,15 @@ time.strftime = py.PY_def.fromJSON(function () {
return py.PY_call(py.PY_getAttr(d, 'strftime'), [args.format]);
});
var args = _.map(('year month day '
+ 'years months weeks days '
var args = _.map(('year month day hour minute second '
+ 'years months weeks days hours minutes seconds '
+ 'weekday leapdays yearday nlyearday').split(' '), function (arg) {
switch (arg) {
case 'years':case 'months':case 'days':case 'leapdays':case 'weeks':
case 'years':case 'months':case 'days':case 'leapdays':case 'weeks':
case 'hours':case 'minutes':case 'seconds':
return [arg, zero];
case 'year':case 'month':case 'day':case 'weekday':
case 'hour':case 'minute':case 'second':
case 'yearday':case 'nlyearday':
return [arg, null];
default:
@@ -699,7 +877,8 @@ var relativedelta = py.type('relativedelta', null, {
this._has_time = 0;
},
__add__: function (other) {
if (!py.PY_isInstance(other, datetime.date)) {
if (!(py.PY_isInstance(other, datetime.date) ||
py.PY_isInstance(other, datetime.datetime))) {
return py.NotImplemented;
}
// TODO: test this whole mess
@@ -730,6 +909,12 @@ var relativedelta = py.type('relativedelta', null, {
day: py.float.fromJSON(day)
};
if (py.PY_isInstance(other, datetime.datetime)) {
repl.hour = py.float.fromJSON(asJS(this.ops.hour) || asJS(other.hour));
repl.minute = py.float.fromJSON(asJS(this.ops.minute) || asJS(other.minute));
repl.second = py.float.fromJSON(asJS(this.ops.second) || asJS(other.second));
}
var days = asJS(this.ops.days);
if (py.PY_isTrue(this.ops.leapdays) && month > 2 && _utils.isleap(year)) {
days += asJS(this.ops.leapdays);
@@ -738,7 +923,10 @@ var relativedelta = py.type('relativedelta', null, {
var ret = py.PY_add(
py.PY_call(py.PY_getAttr(other, 'replace'), repl),
py.PY_call(datetime.timedelta, {
days: py.float.fromJSON(days)
days: py.float.fromJSON(days),
hours: py.float.fromJSON(asJS(this.ops.hours)),
minutes: py.float.fromJSON(asJS(this.ops.minutes)),
seconds: py.float.fromJSON(asJS(this.ops.seconds))
})
);
@@ -776,10 +964,16 @@ var relativedelta = py.type('relativedelta', null, {
months: py.PY_negative(this.ops.months),
days: py.PY_negative(this.ops.days),
leapdays: this.ops.leapdays,
hours: py.PY_negative(this.ops.hours),
minutes: py.PY_negative(this.ops.minutes),
seconds: py.PY_negative(this.ops.seconds),
year: this.ops.year,
month: this.ops.month,
day: this.ops.day,
weekday: this.ops.weekday
weekday: this.ops.weekday,
hour: this.ops.hour,
minute: this.ops.minute,
second: this.ops.second
});
}
});
@@ -340,6 +340,93 @@ QUnit.module('core', function () {
});
QUnit.test('add', function (assert) {
assert.expect(2);
assert.strictEqual(
py.eval("(datetime.datetime(2017, 4, 18, 9, 32, 15).add(hours=2, minutes=30, " +
"seconds=10)).strftime('%Y-%m-%d %H:%M:%S')", pyUtils.context()),
'2017-04-18 12:02:25'
);
assert.strictEqual(
py.eval("(datetime.date(2017, 4, 18).add(months=1, years=3, days=5))" +
".strftime('%Y-%m-%d')", pyUtils.context()),
'2020-05-23'
);
});
QUnit.test('subtract', function(assert) {
assert.expect(2);
assert.strictEqual(
py.eval("(datetime.datetime(2017, 4, 18, 9, 32, 15).subtract(hours=1, minutes=5, " +
"seconds=33)).strftime('%Y-%m-%d %H:%M:%S')", pyUtils.context()),
'2017-04-18 08:26:42'
);
assert.strictEqual(
py.eval("(datetime.date(2017, 4, 18).subtract(years=5, months=1, days=1))" +
".strftime('%Y-%m-%d')", pyUtils.context()),
'2012-03-17'
);
})
QUnit.test('start_of/end_of', function (assert) {
assert.expect(26);
var datetime = pyUtils.context().datetime;
// Ain't that a kick in the head?
var _date = datetime.date.fromJSON(2281, 10, 11);
var _datetime = datetime.datetime.fromJSON(2281, 10, 11, 22, 33, 44);
var ctx = {
_date: _date,
_datetime: _datetime,
date: datetime.date,
datetime: datetime.datetime
};
// Start of period
// Dates first
assert.ok(py.eval('_date.start_of("year") == date(2281, 1, 1)', ctx));
assert.ok(py.eval('_date.start_of("quarter") == date(2281, 10, 1)', ctx));
assert.ok(py.eval('_date.start_of("month") == date(2281, 10, 1)', ctx));
assert.ok(py.eval('_date.start_of("week") == date(2281, 10, 10)', ctx));
assert.ok(py.eval('_date.start_of("day") == date(2281, 10, 11)', ctx));
assert.throws(function () {
py.eval('_date.start_of("hour")', ctx);
}, /^Error: ValueError:/);
// Datetimes
assert.ok(py.eval('_datetime.start_of("year") == datetime(2281, 1, 1)', ctx));
assert.ok(py.eval('_datetime.start_of("quarter") == datetime(2281, 10, 1)', ctx));
assert.ok(py.eval('_datetime.start_of("month") == datetime(2281, 10, 1)', ctx));
assert.ok(py.eval('_datetime.start_of("week") == datetime(2281, 10, 10)', ctx));
assert.ok(py.eval('_datetime.start_of("day") == datetime(2281, 10, 11)', ctx));
assert.ok(py.eval('_datetime.start_of("hour") == datetime(2281, 10, 11, 22, 0, 0)', ctx));
assert.throws(function () {
py.eval('_datetime.start_of("cheese")', ctx);
}, /^Error: ValueError:/);
// End of period
// Dates
assert.ok(py.eval('_date.end_of("year") == date(2281, 12, 31)', ctx));
assert.ok(py.eval('_date.end_of("quarter") == date(2281, 12, 31)', ctx));
assert.ok(py.eval('_date.end_of("month") == date(2281, 10, 31)', ctx));
assert.ok(py.eval('_date.end_of("week") == date(2281, 10, 16)', ctx));
assert.ok(py.eval('_date.end_of("day") == date(2281, 10, 11)', ctx));
assert.throws(function () {
py.eval('_date.start_of("hour")', ctx);
}, /^Error: ValueError:/);
// Datetimes
assert.ok(py.eval('_datetime.end_of("year") == datetime(2281, 12, 31, 23, 59, 59)', ctx));
assert.ok(py.eval('_datetime.end_of("quarter") == datetime(2281, 12, 31, 23, 59, 59)', ctx));
assert.ok(py.eval('_datetime.end_of("month") == datetime(2281, 10, 31, 23, 59, 59)', ctx));
assert.ok(py.eval('_datetime.end_of("week") == datetime(2281, 10, 16, 23, 59, 59)', ctx));
assert.ok(py.eval('_datetime.end_of("day") == datetime(2281, 10, 11, 23, 59, 59)', ctx));
assert.ok(py.eval('_datetime.end_of("hour") == datetime(2281, 10, 11, 22, 59, 59)', ctx));
assert.throws(function () {
py.eval('_datetime.end_of("cheese")', ctx);
}, /^Error: ValueError:/);
});
QUnit.test('relativedelta', function (assert) {
assert.expect(5);