Skip to content

Commit

Permalink
OpenHistoricalMap/issues#626 decimaldate update, locale sensitive ran…
Browse files Browse the repository at this point in the history
…ge picker, date outputs from Intl.DateTimeFormat
  • Loading branch information
gregallensworth committed Nov 2, 2023
1 parent dbbda56 commit 8d6003e
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 207 deletions.
190 changes: 109 additions & 81 deletions decimaldate.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,134 +4,162 @@

const decimaldate = {};

decimaldate.DECIMALPLACES = 6;
decimaldate.DECIMALPLACES = 5;

decimaldate.RE_YEARMONTHDAY = /^(\-?\+?)(\d+)\-(\d\d)\-(\d\d)$/;

decimaldate.iso2dec = (isodate) => {
// parse the date into 3 integers and maybe a minus sign
// validate that it's a valid date
const datepieces = isodate.match(decimaldate.RE_YEARMONTHDAY);
if (! datepieces) throw (`Invalid date format ${isodate}`);

[plusminus, yearstring, monthstring, daystring] = datepieces.slice(1);
if (! decimaldate.isvalidmonth(monthstring) || ! decimaldate.isvalidmonthday(yearstring, monthstring, daystring)) throw `Invalid date ${isodate}`;

let decbit = decimaldate.proportionofdayspassed(yearstring, monthstring, daystring);
if (plusminus == '-') decbit = 1 - decbit;

let yeardecimal = parseInt(yearstring) + decbit;
if (plusminus == '-') yeardecimal *= -1;
if (! datepieces) throw new Error(`iso2dec() malformed date ${isodate}`);

const [plusminus, yearstring, monthstring, daystring] = datepieces.slice(1);
const monthint = parseInt(monthstring);
const dayint = parseInt(daystring);
let yearint = plusminus == '-' ? -1 * parseInt(yearstring) : parseInt(yearstring);
if (yearint <= 0) yearint -= 1; // ISO 8601 shift year<=0 by 1, 0=1BCE, -1=2BCE; we want proper negative integer
if (! decimaldate.isvalidmonthday(yearint, monthint, dayint)) throw new Error(`iso2dec() invalid date ${isodate}`);

// number of days passed = decimal portion
// if BCE <=0 then count backward from the end of the year, instead of forward from January
const decbit = decimaldate.proportionofdayspassed(yearint, monthint, dayint);

let decimaloutput;
if (yearint < 0) {
// ISO 8601 shift year<=0 by 1, 0=1BCE, -1=2BCE; we want string version
// so it's 1 to get from the artificially-inflated integer (string 0000 => -1 for math, +1 to get back to 0)
decimaloutput = 1 + 1 + yearint - (1 - decbit);
}
else {
decimaloutput = yearint + decbit;
}

return parseFloat(yeardecimal.toFixed(decimaldate.DECIMALPLACES));
// round to standardized number of decimals
decimaloutput = parseFloat(decimaloutput.toFixed(decimaldate.DECIMALPLACES));
return decimaloutput;
};


decimaldate.dec2iso = (decdate) => {
// strip the integer/year part
// find how many days were in this year, multiply back out to get the day-of-year number
if (decdate >= 0) {
yearint = parseInt(Math.abs(Math.floor(decdate)));
plusminus = '';
// remove the artificial +1 that we add to make positive dates look intuitive
const truedecdate = decdate - 1;
const ispositive = truedecdate > 0;

// get the integer year
if (ispositive) {
yearint = Math.floor(truedecdate) + 1;
}
else {
yearint = parseInt(Math.abs(Math.ceil(decdate)));
plusminus = '-';
yearint = -Math.abs(Math.floor(truedecdate)); // ISO 8601 shift and year<=0 by 1, 0=1BCE, -1=2BCE
}

const yearstring = yearint + "";
const dty = decimaldate.daysinyear(yearstring);
let targetday = dty * (Math.abs(decdate) % 1);
targetday = decdate >= 0 ? Math.ceil(targetday) : Math.floor(targetday);
if (decdate < 0) targetday = dty - targetday;
// how many days in year X decimal portion = number of days into the year
// if it's <0 then we count backward from the end of the year, instead of forward into the year
const dty = decimaldate.daysinyear(yearint);
let targetday = dty * (Math.abs(truedecdate) % 1);
if (ispositive) targetday = Math.ceil(targetday);
else targetday = dty - Math.floor(targetday);

// count up days months at a time, until we reach our target month
// the the remainder days is the day of the month, offset by 1 cuz we count from 0
const months = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'];
let monthstring;
// then the remainder (days) is the day of that month
let monthint;
let dayspassed = 0;
for (var i=0, l=months.length; i<l; i++) {
monthstring = months[i];

const dtm = decimaldate.daysinmonth(yearstring, monthstring);
for (let m = 1; m <= 12; m++) {
monthint = m;
const dtm = decimaldate.daysinmonth(yearint, monthint);
if (dayspassed + dtm < targetday) {
dayspassed += dtm;
}
else {
break;
}
}

const daynumber = targetday - dayspassed;
const daystring = (daynumber < 10 ? "0" : "") + daynumber;

return `${plusminus}${yearstring}-${monthstring}-${daystring}`;
const dayint = targetday - dayspassed;

// make string output
// months and day as 2 digits
// ISO 8601 shift year<=0 by 1, 0=1BCE, -1=2BCE
const monthstring = monthint.toString().padStart(2, '0');
const daystring = dayint.toString().padStart(2, '0');
let yearstring;
if (yearint > 0) yearstring = yearint.toString().padStart(4, '0'); // just the year as 4 digits
else if (yearint == -1) yearstring = (Math.abs(yearint + 1).toString().padStart(4, '0')); // BCE offset by 1 but do not add a - sign
else yearstring = '-' + (Math.abs(yearint + 1).toString().padStart(4, '0')); // BCE offset by 1 and add - sign

return `${yearstring}-${monthstring}-${daystring}`;
};


decimaldate.isvalidmonth = (monthstring) => {
validmonths = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'];
return validmonths.indexOf(monthstring) != -1;
};
decimaldate.isvalidmonthday = (yearint, monthint, dayint) => {
if (yearint != parseInt(yearint) || yearint == 0) return false;
if (monthint != parseInt(monthint)) return false;
if (dayint != parseInt(dayint)) return false;

if (monthint < 1 || monthint > 12) return false;
if (dayint < 1) return false;

const dtm = decimaldate.daysinmonth(yearint, monthint);
if (! dtm) return false;
if (dayint > dtm) return false;

decimaldate.isvalidmonthday = (yearstring, monthstring, daystring) => {
days = parseInt(daystring);
if (isNaN(days)) return false;
if (days < 0) return false;
const maxdays = decimaldate.daysinmonth(yearstring, monthstring);
if (! maxdays || days > maxdays) return false;
return true;
};


decimaldate.proportionofdayspassed = (yearstring, monthstring, daystring) => {
// count the number of days to get to this day of this month
decimaldate.proportionofdayspassed = (yearint, monthint, dayint) => {
// count the number of days to get through the prior months
let dayspassed = 0;
['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12']
.forEach((tms) => {
if (tms < monthstring) dayspassed += decimaldate.daysinmonth(yearstring, tms);
});
dayspassed += parseInt(daystring);

// subtract 1 cuz day 0 is January 1 and not January 0
// add 0.5 to get us 12 noon
dayspassed -= 1;
dayspassed += 0.5;

// divide by days in year, to get decimal portion since noon of Jan 1
const dty = decimaldate.daysinyear(yearstring);
for (let m = 1; m < monthint; m++) {
const dtm = decimaldate.daysinmonth(yearint, m);
dayspassed += dtm;
}

// add the leftover days not in a prior month
// but minus 0.5 to get us to noon of the target day, as opposed to the end of the day
dayspassed = dayspassed + dayint - 0.5;

// divide by days in year, to get decimal portion
// even January 1 is 0.5 days in since we snap to 12 noon
const dty = decimaldate.daysinyear(yearint);
return dayspassed / dty;
};


decimaldate.daysinmonth = (yearstring, monthstring) => {
monthdaycounts = {
'01': 31,
'02': 28, // February
'03': 31,
'04': 30,
'05': 31,
'06': 30,
'07': 31,
'08': 31,
'09': 30,
'10': 31,
'11': 30,
'12': 31,
decimaldate.daysinmonth = (yearint, monthint) => {
const monthdaycounts = {
1: 31,
2: 28, // February
3: 31,
4: 30,
5: 31,
6: 30,
7: 31,
8: 31,
9: 30,
10: 31,
11: 30,
12: 31,
};

if (decimaldate.isleapyear(yearstring)) monthdaycounts['02'] = 29;
if (decimaldate.isleapyear(yearint)) monthdaycounts[2] = 29;

return monthdaycounts[monthstring];
return monthdaycounts[monthint];
};


decimaldate.daysinyear = (yearstring) => {
return decimaldate.isleapyear(yearstring) ? 366 : 365;
decimaldate.daysinyear = (yearint) => {
return decimaldate.isleapyear(yearint) ? 366 : 365;
};


decimaldate.isleapyear = (yearstring) => {
yearnumber = parseInt(yearstring);
decimaldate.isleapyear = (yearint) => {
if (yearint != parseInt(yearint) || yearint == 0) throw new Error(`isleapyear() invalid year ${yearint}`);

// don't forget BCE; there is no 0 so leap years are -1, -5, -9, ..., -2001, -2005, ...
// just add 1 to the year to correct for this, for this purpose
const yearnumber = yearint > 0 ? yearint : yearint + 1;

const isleap = yearnumber % 4 == 0 && (yearnumber % 100 != 0 || yearnumber % 400 == 0);
return isleap;
};
4 changes: 3 additions & 1 deletion leaflet-ohm-timeslider.css
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,10 @@
display: flex;
}
.leaflet-ohm-timeslider div.leaflet-ohm-timeslider-rangeinputs div.leaflet-ohm-timeslider-rangeinputs-title {
line-height: 2;
}
.leaflet-ohm-timeslider div.leaflet-ohm-timeslider-rangeinputs div.leaflet-ohm-timeslider-rangeinputs-separator {
line-height: 2;
}
.leaflet-ohm-timeslider div.leaflet-ohm-timeslider-rangeinputs div.leaflet-ohm-timeslider-rangeinputs-mindate {
padding-left: 10px;
Expand Down Expand Up @@ -434,7 +436,7 @@
}

.leaflet-ohm-timeslider-modal .leaflet-ohm-timeslider-modal-content .leaflet-ohm-timeslider-modal-body {
padding: 20px 16px 0 16px;
padding: 0 16px 0 16px;
font-size: 13px;
}

Expand Down
Loading

0 comments on commit 8d6003e

Please sign in to comment.