-
Notifications
You must be signed in to change notification settings - Fork 0
/
MilesianUser.js
280 lines (272 loc) · 12.5 KB
/
MilesianUser.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
/* MilesianUserFunctions : Enter and display in Google Sheets dates following Milesian calendar conventions
For use as a Google Application Script associated with a Google sheet.
This package is consistent with the similar Basic and VBA modules for OpenOffice and MS Excel.
Functions are aimed at extending Date & Time functions, and use similar parameters syntax in English
Versions GAS:
M2018-02-19: first release
M2018-06-12: update comments and adapt to MilesianPrimitives
M2019-01-15: solar intercalation rule is as of Gregorian
functions:
MILESIAN_IS_LONG_YEAR
MILESIAN_DATE
MILESIAN_YEAR, MILESIAN_MONTH, MILESIAN_DAY, MILESIAN_DISPLAY
MILESIAN_UTCYEAR, MILESIAN_UTCMONTH, MILESIAN_UTCDAY, MILESIAN_UTCDISPLAY
TIMEZONE_OFFSET
MILESIAN_MONTH_SHIFT,MILESIAN_MONTH_END
JULIAN_EPOCH_COUNT,JULIAN_EPOCH_DATE
*/
/* Copyright Miletus SARL 2018. www.calendriermilesien.org
No warranty.
May be used for personal or professional purposes.
If transmitted or integrated, even with changes, present header shall be maintained in full.
*/
/* Implementation notes:
When translated to GAS, the Google Sheets dates are considered local time. This gives generally non expected effects.
*/
var HighYear = 99999, // Upper limit that yields a Google Sheet date value is 99999 as Gregorian year.
LowYear = -2, // Bottom limit. We add the real day limit
LowCount = -694324; // The lowest MS Count that represents a valid date under Google Sheet.
//#Part 1: internal procedures -> brand other functions, to be added to CBCCE.
/**
* Positive modulo. Divisor must be positive.
* @param {number} Dividend. If negative, the result shall be positive.
* @param {number} Divisor, must be positive, non zero, else return "undefined"
* @return {number} The modulo, positive or zero.
*/
function positiveModulo (dividend, divisor) {
if (divisor <= 0) return ; // Stop execution and return "Undefined"
while (dividend < 0) dividend += divisor;
while (dividend >= divisor) dividend -= divisor;
return dividend
}
function MSCount_ (theDate) { // Translate a GS Date object into an Google Sheet counter.
const MSDaystoPosix = 25569;
return (theDate.valueOf() / Chronos.DAY_UNIT) + MSDaystoPosix;
}
function pad(number) { // utility function, pad 2-digit integer numbers. No control.
return ( number < 10 ) ? ('0' + number) : number;
}
//#Part 2: a function not more used internally, but available to users
/**
* Whether the year is a milesian long year (366 days)
* @param {integer} the year in question; may be positive, 0 or negative.
* @return {boolean} true if long year, false if not, error if year is not integer
*/
function MILESIAN_IS_LONG_YEAR(Year) {
//Is year Year a 366 days year, i.e. a year just before a bissextile year following the Milesian rule.
if (Year !== Math.round(Year) || Year < LowYear || Year > HighYear) throw "MILESIAN_IS_LONG_YEAR: Invalid argument: " + Year; //Check that we have an integer numeric value
Year += 1;
return (positiveModulo (Year,4) == 0 && (positiveModulo (Year,100) !== 0 || positiveModulo(Year, 400) == 0 ));
}
//#Part 3: Compute date (local time) from milesian parameters
/**
* Date computed from Milesian components. Error raised if components are irrelevant or out of range.
* @param {integer} year, positive, 0 or negative (from -2 to 100000)
* @param {integer} month, 1 to 12.
* @param {integer} day in month, 1 to 31. Date validity control is performed.
* @return {date} a Google Sheets day number (0 for 30 december 1899)
*/
function MILESIAN_DATE(Year, Month, DayInMonth) {
//Date number at 00:00 from a Milesian date given as year (positive or negative), month, daynumber in month
//Check that Milesian date is OK
if (Year !== Math.round(Year) || Month !== Math.round(Month) || DayInMonth !== Math.round(DayInMonth))
throw "MILESIAN_DATE: Invalid argument: " + Year +" "+ Month +" "+ DayInMonth;
if (Year >= LowYear && Year <= HighYear && Month > 0 && Month < 13 && DayInMonth > 0 && DayInMonth < 32) { //Basic filter
if (DayInMonth < 31 || (Month % 2 == 0) && (Month < 12 || MILESIAN_IS_LONG_YEAR(Year))) {
var theDate = setUTCDateFromMilesian (Year, Month-1, DayInMonth);
var MyCount = MSCount_ (theDate);
if (MyCount < LowCount) throw "MILESIAN_DATE: Out-of-range argument: " + Year +" "+ Month +" "+ DayInMonth
else return MyCount;
}
else // Case where date elements do not build a correct milesian date
throw "MILESIAN_DATE: Invalid date: " + Year +" "+ Month +" "+ DayInMonth;
}
else // Case where the date elements are outside basic values
throw "MILESIAN_DATE: Out-of-range argument: " + Year +" "+ Month +" "+ DayInMonth;
}
/**
* the last day at 7:30 UTC before a Milesian year. Usefull for doomsday and epact.
* @param {integer} the year in question; may be positive, 0 or negative.
* @return {boolean} date of the day before, as a Google Sheets count.
*/
function MILESIAN_YEAR_BASE(Year) {
if (Year !== Math.round(Year) || Year < LowYear || Year > HighYear) throw "MILESIAN_YEAR_BASE: Invalid argument: " + Year;
var theDate = setUTCDateFromMilesian (Year, 0, 0) ;
return MSCount_(theDate)+ 0.3125;
}
//#Part 4: Extract Milesian elements from Date element, using getMilesianDate (theDate) that gives local time.
/**
* The Milesian year (common era, relative notation) for a given date.
* @param {date} the date being converted.+ 0.3125
* @return {integer} the Milesian year, may be positive, 0 or negative.
*/
function MILESIAN_YEAR(TheDate) {
var R = getMilesianDate (TheDate);
return R.year;
}
/**
* The Milesian month for a given date.
* @param {date} the date being converted.
* @return {integer} the Milesian month number, 1 to 12.
*/
function MILESIAN_MONTH(TheDate) {
var R = getMilesianDate (TheDate);
return R.month+1; // under JS month begin with 0.
}
/**
* The Milesian day in month for a given date.
* @param {date} the date being converted.
* @return {integer} the Milesian day in month, 1 to 31.
*/
function MILESIAN_DAY(TheDate) {
var R = getMilesianDate (TheDate);
return R.date;
}
/**
* The time element of a given date, shows the conversion made between Google Sheets and GS
* @param {date} the date being converted.
* @return {number} a number greater or equal to 0 and lower than 1.
*/
function MILESIAN_TIME(TheDate) {
// In a first attempt, made a decomposition in { Days, Milliseconds }, which is too long an operation.
var R = getMilesianDate (TheDate)
return (R.hours * Chronos.HOUR_UNIT + R.minutes * Chronos.MINUTE_UNIT + R.seconds * Chronos.SECOND_UNIT + R.milliseconds) / Chronos.DAY_UNIT;
}
/**
* A string, displays the date in Milesian calendar.
* @param {date} the date being converted.
* @param {number} optional, if anything bt 0, the time is also displayed.
* @return {string} the milesian date and optionally the time.
*/
function MILESIAN_DISPLAY(TheDate, Wtime) {
var R = getMilesianDate (TheDate);
var S = R.date + " " + (R.month+1) + "m " + R.year + (Wtime ? " " + pad(R.hours)+":"+pad(R.minutes)+":"+pad(R.seconds) : "");
return S;
}
/**
* The Milesian year for a given date, UTC.
* @param {date} the date being converted.
* @return {integer} the Milesian year, may be positive, 0 or negative.
*/
function MILESIAN_UTCYEAR(TheDate) {
var R = getUTCMilesianDate (TheDate);
return R.year;
}
/**
* The Milesian month for a given date, UTC.
* @param {date} the date being converted.
* @return {integer} the Milesian month number, 1 to 12.
*/
function MILESIAN_UTCMONTH(TheDate) {
var R = getUTCMilesianDate (TheDate);
return R.month+1; // under JS month begin with 0.
}
/**
* The Milesian day in month for a given date, UTC.
* @param {date} the date being converted.
* @return {integer} the Milesian day in month, 1 to 31.
*/
function MILESIAN_UTCDAY(TheDate) {
var R = getUTCMilesianDate (TheDate);
return R.date;
}
/**
* The time element of a given date, UTC
* @param {date} the date being converted.
* @return {number} a number greater or equal to 0 and lower than 1.
*/
function MILESIAN_UTCTIME(TheDate) {
var R = getUTCMilesianDate (TheDate);
return (R.hours * Chronos.HOUR_UNIT + R.minutes * Chronos.MINUTE_UNIT + R.seconds * Chronos.SECOND_UNIT + R.milliseconds) / Chronos.DAY_UNIT;
}
/**
* A string, displays the date in Milesian calendar, UTC.
* @param {date} the date being converted.
* @param {number} optional, if anything bt 0, the time is also displayed.
* @return {string} the milesian date and optionally the time, UTC.
*/
function MILESIAN_UTCDISPLAY(TheDate, Wtime) {
var R = getUTCMilesianDate (TheDate);
var S = R.date + " " + (R.month+1) + "m " + R.year + (Wtime ? " " + pad(R.hours)+":"+pad(R.minutes)+":"+pad(R.seconds) : "");
return S;
}
/**
* The timezone offset of a date, in minutes.
* @param {date} the date in question
* @return {integer} the number of minutes added to local time in order to get UTC
*/
function TIMEZONE_OFFSET(TheDate) { // The timezone offset of a (JS) Date object, at a given date.
return TheDate.getTimezoneOffset();
}
//#Part 5: Computations on milesian months
var
Year_Month_Params = { // to be used in order to shift months, or change lunar calendar epoch without changing lunar age.
timeepoch : 0, // put the timeepoch in the parameter call.
coeff : [
{cyclelength : 12, ceiling : Infinity, subCycleShift : 0, multiplier : 1, target : "year"},
{cyclelength : 1, ceiling : Infinity, subCycleShift : 0, multiplier : 1, target : "month"}
],
canvas : [
{name : "year", init :0},
{name : "month", init : 0}
]
};
/**
* A date several Milesian month later of earlier.
* @param {date} start date.
* @param {number} number of month, positive if later, negative if earlier.
* @return {date} the Milesian date (and time). If start day is 31, target date may be 30.
*/
function MILESIAN_MONTH_SHIFT (TheDate, MonthShift) { //Same date several (milesian) months later of earlier
if (MonthShift !== Math.round(MonthShift)) throw "MILESIAN_MONTH_SHIFT: Invalid argument: " + Monthshift;
var MyMil = getMilesianDate (TheDate) ; // Construct start date
var Shift = cbcceDecompose (MonthShift + MyMil.month,Year_Month_Params) ; // compute new month number and year shift;
var year = MyMil.year + Shift.year; // year of target date
if (year < LowYear || year > HighYear) throw "MILESIAN_MONTH_SHIFT: Out-of-range : " + year + " " + Shift.month
else { // Construct result
if (MyMil.date == 31) // In this case maybe we should change this figure to 30
MyMil.date = ((Shift.month % 2 == 1) && (Shift.month < 11 || MILESIAN_IS_LONG_YEAR(year)) ? 31 : 30);
MyMil.year = year;
MyMil.month = Shift.month;
return MSCount_ (cbcceCompose (MyMil, Milesian_time_params));
}
}
/**
* Date of last day in month several Milesian month later of earlier.
* @param {date} start date.
* @param {number} number of month, positive if later, negative if earlier.
* @return {date} the Milesian date (and time) at end of Milesian month.
*/
function MILESIAN_MONTH_END (TheDate, MonthShift) { //End of month, several (milesian) months later of earlier
if (MonthShift !== Math.round(MonthShift)) throw "MILESIAN_MONTH_END: Invalid argument: " + Monthshift;
var MyMil = getMilesianDate (TheDate) ; // Construct start date
var Shift = cbcceDecompose (MonthShift + MyMil.month, Year_Month_Params) ; // compute new month number and year shift;
var year = MyMil.year + Shift.year; // year of target date
if (year < LowYear || year > HighYear) throw "MILESIAN_MONTH_SHIFT: Out-of-range : " + year + " " + Shift.month
else { // Construct result
MyMil.date = ((Shift.month % 2 == 1) && (Shift.month < 11 || MILESIAN_IS_LONG_YEAR(year)) ? 31 : 30);
MyMil.year = year;
MyMil.month = Shift.month;
return MSCount_ (cbcceCompose (MyMil, Milesian_time_params));
}
}
//#Part 6: Julian Epoch Day conversion functions
var JULIAN_DAY_UTC0_EPOCH_OFFSET = 210866803200000; // Julian Day 0 at 0h00 UTC.
/**
* The number of the Julian day corresponding to a date with time. Always UTC.
* @param {date} the date being converted
* @return {number} the Julian day number
*/
function JULIAN_EPOCH_COUNT(TheDate) {
return (TheDate.getTime() + JULIAN_DAY_UTC0_EPOCH_OFFSET - (12 * Chronos.HOUR_UNIT)) / Chronos.DAY_UNIT;
}
/**
* Compute a date corresponding to a Julian day number.
* @param {number} the Julian day to convert
* @return {date} the corresponding date
*/
function JULIAN_EPOCH_DATE(Julian_Count) {
var MyDate = new Date (0);
MyDate.setTime (Math.round(Julian_Count*Chronos.DAY_UNIT) - JULIAN_DAY_UTC0_EPOCH_OFFSET + 12 * Chronos.HOUR_UNIT);
return MyDate;
}