py-datetime
Version:
Datetime handling the python way (or as close as possible)
323 lines (281 loc) • 8.67 kB
JavaScript
import * as d3TimeFormat from "d3-time-format";
let toMillis = {
milliseconds: 1,
seconds: 1000,
minutes: 1000 * 60,
hours: 1000 * 60 * 60,
days: 1000 * 60 * 60 * 24,
weeks: 1000 * 60 * 60 * 24 * 7,
};
class time {
constructor(hour, minute, second, millisecond) {
let args = {hour, minute, second, millisecond};
if (hour != null && typeof hour != "number") {
// we have a dict
args = hour;
}
["hour", "minute", "second", "millisecond"].forEach(field => {
args[field] = args[field] || 0;
});
Object.assign(this, args);
}
str() {
// we have to set the date to today to avoid any daylight saving nonsense
let ts = dt.datetime.combine(dt.datetime.now(), this);
return d3TimeFormat.timeFormat("%H:%M:%S.%f")(new Date(ts));
}
get __totalMillis() {
return (
this.hour * toMillis.hours +
this.minute * toMillis.minutes +
this.second * toMillis.seconds +
this.millisecond
);
}
valueOf() {
return this.__totalMillis;
}
toString() {
return this.str();
}
toJSON() {
return this.str();
}
}
function timeWrapper(hour, minute, second, millisecond) {
return new time(hour, minute, second, millisecond);
}
class date {
constructor(year, month, day) {
Object.assign(this, {year, month, day});
}
get jsDate() {
return new Date(this.year, this.month - 1, this.day);
}
str() {
return d3TimeFormat.timeFormat("%Y-%m-%d")(this.jsDate);
}
weekday() {
// javascript week starts on sunday, while python one starts on monday
return (this.jsDate.getDay() + 6) % 7;
}
isoweekday() {
return this.weekday() + 1;
}
get __totalMillis() {
return this.jsDate.getTime();
}
valueOf() {
return this.__totalMillis;
}
toString() {
return this.str();
}
toJSON() {
return this.str();
}
}
function dateWrapper(year, month, day) {
return new date(year, month, day);
}
class datetime {
constructor(year, month, day, hour, minute, second, millisecond, utc) {
let args = {};
this.utc = utc;
if (typeof year == "number" && !month && !day) {
// while a dt.datetime(2020) is perfectly valid, it's quite unlikely.
// much more unlikely than having gotten an epoch passed in. convert that to date
year = new Date(year);
}
if (year.year && year.month && year.day) {
["year", "month", "day", "hour", "minute", "second", "millisecond", "utc"].forEach(field => {
let ts = year;
args[field] = ts[field];
});
} else if (year instanceof Date) {
let ts = year;
args = {
year: ts.getFullYear(),
month: ts.getMonth() + 1,
day: ts.getDate(),
hour: ts.getHours(),
minute: ts.getMinutes(),
second: ts.getSeconds(),
millisecond: ts.getMilliseconds(),
};
} else {
args = {year, month, day, hour, minute, second, millisecond};
}
Object.assign(this, args);
}
replace(year, month, day, hour, minute, second, millisecond) {
// returns new date with updated values
let args = {};
if (year && typeof year != "number") {
args = year;
} else {
args = {year, month, day, hour, minute, second, millisecond};
}
let newTs = new datetime(this);
Object.entries(args).forEach(([key, val]) => {
if (val != null) {
newTs[key] = val;
}
});
return newTs;
}
get jsDate() {
if (this.utc) {
return new Date(this.valueOf());
} else {
return new Date(
this.year,
this.month - 1,
this.day || 1,
this.hour || 0,
this.minute || 0,
this.second || 0,
this.millisecond || 0
);
}
}
str() {
return this.strftime("%Y-%m-%d %H:%M:%S.%f");
}
valueOf() {
if (this.utc) {
return Date.UTC(
this.year,
this.month - 1,
this.day || 1,
this.hour || 0,
this.minute || 0,
this.second || 0,
this.millisecond || 0
);
} else {
return this.jsDate.getTime();
}
}
toString() {
return this.str();
}
toJSON() {
return this.str();
}
strftime(format) {
if (this.utc) {
return d3TimeFormat.utcFormat(format)(this.jsDate);
} else {
return d3TimeFormat.timeFormat(format)(this.jsDate);
}
}
time() {
return new time(this.hour, this.minute, this.second, this.millisecond);
}
date() {
return new date(this.year, this.month, this.day);
}
weekday() {
// javascript week starts on sunday, while python one starts on monday
return this.date().weekday();
}
isoweekday() {
return this.weekday() + 1;
}
}
function datetimeWrapper(year, month, day, hour, minute, second, millisecond) {
return new datetime(year, month, day, hour, minute, second, millisecond);
}
datetimeWrapper.strptime = (dateString, format, utc) => {
let parser = utc ? d3TimeFormat.utcParse : d3TimeFormat.timeParse;
let parsed = parser(format)(dateString);
if (!parsed) {
throw `ValueError: time data '${dateString}' does not match format '${format}'`;
}
return utc ? datetimeWrapper.utc(parsed) : new datetime(parsed);
};
datetimeWrapper.now = () => {
return new datetime(new Date());
};
datetimeWrapper.utcnow = () => {
return datetimeWrapper.utc(new Date());
};
datetimeWrapper.combine = (date, time) => {
date = new datetime(date);
Object.assign(date, time);
return date;
};
datetimeWrapper.utc = ts => {
if (typeof ts == "number") {
// while a dt.datetime(2020) is perfectly valid, it's quite unlikely.
// much more unlikely than having gotten an epoch passed in. convert that to date
ts = new Date(ts);
} else if (ts instanceof datetime) {
ts = ts.jsDate;
}
return new datetime(
ts.getUTCFullYear(),
ts.getUTCMonth() + 1,
ts.getUTCDate(),
ts.getUTCHours(),
ts.getUTCMinutes(),
ts.getUTCSeconds(),
ts.getUTCMilliseconds(),
true
);
};
class timedelta {
constructor(days, seconds, milliseconds, minutes, hours, weeks) {
let args = {weeks, days, hours, minutes, seconds, milliseconds};
if (typeof days != "number") {
// we have a dict
args = days;
} else if (Math.abs(days) > 900) {
// we have millis, let's deconstruct into days, hours, minutes, seconds, milliseconds
let totalMillis = days;
args = {};
["days", "hours", "minutes", "seconds", "milliseconds"].forEach(key => {
let multiplier = toMillis[key];
let val = Math.floor(totalMillis / multiplier);
if (val) {
args[key] = val;
totalMillis -= val * multiplier;
}
});
}
["weeks", "days", "hours", "minutes", "seconds", "milliseconds"].forEach(key => {
this[key] = args[key] || 0;
});
}
get __totalMillis() {
let tsFields = ["weeks", "days", "hours", "minutes", "seconds", "milliseconds"];
let millis = tsFields.map(field => this[field] * toMillis[field]);
return millis.reduce((total, current) => total + current);
}
str() {
return d3TimeFormat.timeFormat("%H:%M:%S.%f")(new Date(this));
}
valueOf() {
return this.__totalMillis;
}
toString() {
return this.str();
}
toJSON() {
return this.str();
}
totalSeconds() {
return this.__totalMillis / 1000;
}
}
function timedeltaWrapper(days, seconds, milliseconds, minutes, hours, weeks) {
return new timedelta(days, seconds, milliseconds, minutes, hours, weeks);
}
const dt = {
datetime: datetimeWrapper,
time: timeWrapper,
date: dateWrapper,
timedelta: timedeltaWrapper,
};
export default dt;