I decided in the end to produce my own masks for the date — well, they are actually values in an associative array, but they look very similar to masks. The resulting function "DoMasks" works for all Gregorian dates (and therefore not before 1600 when the Julian calendar applied), and include some further useful masks, including:
- ArrMask.dayabs is the "absolute" day nunber, with 01/01/2001 being Day 1. This function allows differences between dates to be calculated very easily based on the date alone, without worrying about milliseconds, daylight saving, rounding and so forth. Because this first day of the millennium was a Monday, ArrMask.dayabs modulo 7 happily coincides with JScript's "getDay ()".
- ArrMask.dayy returns the day of the year, with 1st January being Day 1.
- ArrMask.ord is the ordinal dates 1st, 2nd, 3rd, 4th, . . . that I was fussing about a little while ago.
- ArrMask.leap is Boolean for leap year, and ArrMask.daysinmonth returns the days in the current month.
- ArrMask.tt returns the traditional "noon" for midday instead of "pm". That is taking control!
- At the bottom of the function are some standard formats of the whole date and/or time.
This single array solves all the problems that I was worrying about, but I understand that it is an idiosyncratic solution. Other masks could easily be added, in particular, UTC codes.
I've left in place the first block, which allows easy testing of the various masks.
At the bottom is a proper integer division algorithm "DoDivision", again using an associative array, to replace the faulty modulo operator in JScript. JScript doesn't treat remainders properly when the dividend is negative — non-zero remainders are conventionally positive in mathematics, but JScript uses negative remainders. For example, JScript claims that
-11 = -4 (mod 7), instead of -11 = 3 (mod 7). In one sense, they are both correct, in that
-11 = 7 * (-1) - 4 and -11 = 7 * (-2) + 3, but only the second is standard, because swapping around confuses the structure of the ideal of all integers that are 3 modulo 7. This is quite serious with dates, because for example JScript reckons that 3 and -11 are unequal modulo 7, whereas they are the same day of the week! In the array produced by the function "DoDivision":
- ArrDivision.r is the remainder, given as a non-negative whole number less than the divisor.
- ArrDivision.q is the corresponding integer quotient. JScript omits the quotient, and again, comparing quotients is meaningless when remainders have been calculated differently for positive and negative integers.
To demonstrate its usefulness, I've also included a further function "DoEuclid" that performs the Euclidean algorithm on two integers.
The "DoMasks" function can be used by copying it into any JScript, but it uses the three functions "DoDivision", "DoOrdinalEnding" and "DoBuffer", which need to be copied as well. The function "DoDate" creates a date object from two of the standard formats in "DoMasks" — it stands on its own, and allows checking by reversing the processes.
[code]@Script JScript
// Julianon 17/08/15 Six functions for dates and integer division.
// The following 30 lines are only for testing and may be deleted.
DOpus.Output ("- - - - - - - - >")
ObjDateX = DoDate ("160321@153409.456789")
DOpus.Output (DoMasks (ObjDateX).verbose + ", " + DoMasks (ObjDateX).standardss + "." + DoMasks (ObjDateX).fff)
DOpus.Output ("Compare DoMasks(ObjDateX).dayabs (modulo 7) = " + DoMasks (ObjDateX).dayabs + " (modulo 7) = " + DoDivision (DoMasks (ObjDateX).dayabs, 7).r + ", and ObjDateX.getDay() = " + ObjDateX.getDay ())
DOpus.Output ("- - - - - - - -")
ObjDateY = new Date
DOpus.Output (DoMasks (ObjDateY).verbose + ", " + DoMasks (ObjDateY).hhmmss + ". This is the " + DoMasks (ObjDateY).dayy + DoOrdinalEnding (DoMasks (ObjDateY).dayy) + " day of the year.")
DOpus.Output ("Compare DoMasks(ObjDateY).dayabs (modulo 7) = " + DoMasks (ObjDateY).dayabs + " (modulo 7) = " + DoDivision (DoMasks (ObjDateY).dayabs, 7).r + ", and ObjDateY.getDay() = " + ObjDateY.getDay ())
var TheTemp = DoMasks (ObjDateX).dayabs - DoMasks (ObjDateY).dayabs
DOpus.Output ("There are " + TheTemp + " days between this date and the previous date.")
DOpus.Output ("- - - - - - - -")
ObjDateZ = DoDate ("29/02/1800 12:00")
DOpus.Output (DoMasks (ObjDateZ).ddMMyyyy + ", " + DoMasks (ObjDateZ).hhmm)
DOpus.Output ("Compare DoMasks(ObjDateZ).dayabs (modulo 7) = " + DoMasks (ObjDateZ).dayabs + " (modulo 7) = " + DoDivision (DoMasks (ObjDateZ).dayabs, 7).r + ", and ObjDateZ.getDay() = " + ObjDateZ.getDay ())
DOpus.Output ("- - - - - - - -")
DOpus.Output ("-11 modulo 7 using JScript's modulo function: -11 % 7 = " + -11 % 7)
Arr7 = DoDivision (-11, 7)
DOpus.Output ("-11 divided by 7 using the DoDivision function below: " + Arr7.n + " divided by " + Arr7.d + " = " + Arr7.q + " remainder " + Arr7.r)
DOpus.Output ("- - - - - - - -")
Euclid = DoEuclid (21, -175)
DOpus.Output ("HCF(" + Euclid.a + ", " + Euclid.b + ") = " + Euclid.HCF + " and LCM(" + Euclid.a + ", " + Euclid.b + ") = " + Euclid.LCM)
TheTemp = Euclid.a * Euclid.x + Euclid.b * Euclid.y
DOpus.Output (Euclid.a + " times " + Euclid.x + " + " + Euclid.b + " times " + Euclid.y + " = " + Euclid.a * Euclid.x + " + " + Euclid.b * Euclid.y + " = " + TheTemp)
DOpus.Output ("< - - - - - - - -")
// FUNCTION DoMasks (ObjDate) THIS REQUIRES DoDivision (TheDividend, TheDivisor) AND DoOrdinalEnding (TheNumber) AND DoBuffer (TheNumber, TheLength).
function DoMasks (ObjDate) { // ObjDate must be a date object.
ArrMonthNames = new Array ("January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December")
ArrDayNames = new Array ("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday")
ArrDaysInMonths = new Array (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) // The February value will be changed if it is a leap year.
var nn = 0
ArrMask = new Array // An associative array that will carry all the various masks, and is returned.
ArrMask.yyyy = ObjDate.getFullYear () // STEP 1: Input the data from ObjDate object:
ArrMask.M = ObjDate.getMonth () + 1
ArrMask.d = ObjDate.getDate ()
ArrMask.day = ObjDate.getDay ()
ArrMask.H = ObjDate.getHours ()
ArrMask.m = ObjDate.getMinutes ()
ArrMask.s = ObjDate.getSeconds ()
ArrMask.ms =ObjDate.getMilliseconds ()
ArrMask.yy = DoBuffer (ArrMask.yyyy, 2) // STEP 2: The two-digit masks and milliseconds.
ArrMask.y = 1 * ArrMask.yy
ArrMask.MM = DoBuffer (ArrMask.M, 2)
ArrMask.dd = DoBuffer (ArrMask.d, 2)
ArrMask.HH = DoBuffer (ArrMask.H, 2)
ArrMask.mm = DoBuffer (ArrMask.m, 2)
ArrMask.ss = DoBuffer (ArrMask.s, 2)
ArrMask.fff = DoBuffer (ArrMask.ms, 3)
ArrMask.h = ArrMask.H // STEP 3: 12 hour time with AM and PM and NOON.
ArrMask.hh = DoBuffer (ArrMask.h, 2)
ArrMask.tt = "am"
if (ArrMask.H > 12)
ArrMask.h = ArrMask.h - 12
if (ArrMask.H > 11)
ArrMask.tt = "pm"
if (ArrMask.H == 12 && ArrMask.m == 0 && ArrMask.s == 0 && ArrMask.ms == 0)
ArrMask.tt = "noon"
ArrMask.dddd = ArrDayNames [ArrMask.day] // STEP 4: The names and abbreviations of the month and the day.
ArrMask.ddd = String (ArrMask.dddd).substr (0, 3)
ArrMask.MMMM = ArrMonthNames [ArrMask.M - 1]
ArrMask.MMM = String (ArrMask.MMMM).substr (0, 3)
ArrMask.ord = ArrMask.d + DoOrdinalEnding (ArrMask.d) // STEP 5: The ordinal dates 1st, 2nd, 3rd, 4th, . . .
ArrMask.leap = false // STEP 6: A Boolean value for the leap year.
if (ArrMask.yyyy % 4 == 0 && (!(ArrMask.yyyy % 100 == 0) || ArrMask.yyyy % 400 == 0)) {// Is it a leap year?
ArrMask.leap = true
ArrDaysInMonths [1] = 29 // Change February to 29 days in leap years.
}
ArrMask.daysinmonth = ArrDaysInMonths [ArrMask.M-1] // STEP 7: The number of days in the date's month.
ArrMask.dayy = ArrMask.d // STEP 8: ArrMask.dayy will be the day of the year, with 01/01 being yday 1.
for (nn = 1; nn < ArrMask.M; nn++)
ArrMask.dayy = ArrMask.dayy + ArrDaysInMonths [nn - 1] // The leap year correction has already been made.
// STEP 9: ArrMask.dayabs will be the second millenium day absolute, with 01/01/2001 being day 1, and earlier days being negative.
ArrEuclid400 = DoDivision (ArrMask.yyyy, 400)
ArrMask.dayabs = 146097 * (ArrEuclid400.q - 5) // There are 146097 day in each Gregorian 400-year period
ArrMask.dayabs = ArrMask.dayabs + (365 * (ArrEuclid400.r - 1)) // Add 365 days for each completed year.
for (nn = 0; nn < ArrEuclid400.r; nn = nn + 4) {
if (!(nn % 400 == 100 || nn % 400 == 200 || nn % 400 == 300))
ArrMask.dayabs = ArrMask.dayabs + 1 // Add 1 more day for each completed leap year after 2000.
}
ArrMask.dayabs = ArrMask.dayabs + ArrMask.dayy // Add the day-of-the-year function.
ArrMask.dayabs = ArrMask.dayabs - 1 // Subtract 1 to make 01/01/2001 day 1.
// STEP 10: Some common date formats.
ArrMask.ddMMyy = ArrMask.dd + "/" + ArrMask.MM + "/" + ArrMask.yy // With dashes.
ArrMask.ddMMyyyy = ArrMask.dd + "/" + ArrMask.MM + "/" + ArrMask.yyyy // With dashes.
ArrMask.yyMMdd = ArrMask.yy + ArrMask.MM + ArrMask.dd // Without dashes.
ArrMask.HHmm = ArrMask.HH + ":" + ArrMask.mm // With a colon.
ArrMask.HHmmss = ArrMask.HH + ":" + ArrMask.mm + ":" + ArrMask.ss // With colons.
ArrMask.hhmm = ArrMask.hh + ":" + ArrMask.mm + " " + ArrMask.tt // With a colon.
ArrMask.hhmmss = ArrMask.hh + ":" + ArrMask.mm + ":" + ArrMask.ss + " " + ArrMask.tt // With colons.
ArrMask.standard = ArrMask.ddMMyy + " " + ArrMask.HHmm // For listers.
ArrMask.standardss = ArrMask.ddMMyy + " " + ArrMask.HHmmss // Add the seconds.
ArrMask.reverse = ArrMask.yyMMdd + "@" + ArrMask.HH + ArrMask.mm + ArrMask.ss // For use in filenames.
ArrMask.verbose = ArrMask.dddd + ", " + ArrMask.ord + " " + ArrMask.MMMM + " " + ArrMask.yyyy // Ceremonial.
return ArrMask
}
// FUNCTION DoDivision (TheDividend, TheDivisor)*************************************************************
function DoDivision (TheDividend, TheDivisor) { // Intended only for integers, with TheDivisor nonzero.
ArrDivision = new Array // This is an associative array that will carry the four elements, and be returned.
ArrDivision.n = TheDividend // This is the dividend
ArrDivision.d = TheDivisor // This is the divisor
ArrDivision.r = TheDividend % TheDivisor
if (ArrDivision.r < 0) // The remainder should be a positive number.
ArrDivision.r = ArrDivision.r + Math.abs (TheDivisor) // This is the remainder
ArrDivision.q = Math.round ((TheDividend - ArrDivision.r) / TheDivisor) // This is the quotient.
return ArrDivision
}
// FUNCTION DoOrdinalEnding (TheNumber)***********************************************************************
function DoOrdinalEnding (TheNumber) { // Intended only for non-negative whole numbers.
StrOrdEnding = new String ("th") // This will be the ordinal ending, and will be returned.
var TheMod10 = TheNumber % 10
var TheMod100 = TheNumber % 100
if (TheMod10 == 1 && !(TheMod100 == 11))
StrOrdEnding = "st"
if (TheMod10 == 2 && !(TheMod100 == 12))
StrOrdEnding = "nd"
if (TheMod10 == 3 && !(TheMod100 == 13))
StrOrdEnding = "rd"
return StrOrdEnding
}
// FUNCTION DoBuffer (TheNumber, TheLength)******************************************************************
function DoBuffer (TheNumber, TheLength) { // Intended only for two non-negative whole numbers.
StrNumber = new String ("") // This will be the buffered or truncated number.
StrNumber = TheNumber.toString ()
while (StrNumber.length < TheLength) // This bit adds zeroes at the front if the string is too short.
StrNumber = "0" + StrNumber
if (StrNumber.length > TheLength) // This bit truncates from the left if the string is too long.
StrNumber = StrNumber.substr (StrNumber.length - TheLength)
return StrNumber
}
// FUNCTION DoDate (TheDate)*********************************************************************************
// TheDate may in one of two forms: Either "27/8/2034 1:23:45.678" or "340827@012345.678"
// where if any time element is missing, all subsequent time elements are missing.
function DoDate (TheDate) {
var nn = 0
var TheTemp = new String ("")
ArrFull = TheDate.split (" ")
ArrDate = ArrFull [0].split ("/")
// FIRST FORM: d/M/yyyy H:m:s.fff where d, M, H, m, s may be dd, MM, HH, mm, ss respectively, but .fff is a decimal.
if (ArrDate.length == 3) {
TheTemp = ArrDate [0]
ArrDate [0] = ArrDate [2]
ArrDate [2] = TheTemp
for (nn = 3; nn < 7; nn++)
ArrDate [nn] = 0
if (!(ArrFull [1] == undefined)) {
ArrTime = ArrFull [1].split (":")
ArrDate [3] = ArrTime [0]
if (ArrTime.length > 1)
ArrDate [4] = ArrTime [1]
if (ArrTime.length > 2) {
ArrDate [5] = Math.floor (ArrTime [2])
ArrDate [6] = Math.round (1000 * (ArrTime [2] - ArrDate [5]))
}
}
} else {
// SECOND FORM: yyMMdd@mmHHss.fff where each is 2-buffered, and 20yy is assumed, except that .fff is a decimal.
ArrFull = TheDate.split ("@")
ArrDate = new Array
ArrDate [0] = ArrFull [0].substr (0, 2)
ArrDate [1] = ArrFull [0].substr (2, 2)
ArrDate [2] = ArrFull [0].substr (4, 2)
ArrDate [3] = ArrFull [1].substr (0, 2)
ArrDate [4] = ArrFull [1].substr (2, 2)
ArrDate [5] = ArrFull [1].substr (4, 2)
ArrDate [6] = Math. round (1000 * ArrFull [1].substr (6))
}
if ((ArrDate [0]).toString ().length == 2) {
ArrDate [0] = "20" + ArrDate [0]
DOpus.Output ("WARNING: The date " + TheDate + " was interpreted as being in the year " + ArrDate [0] + ".")
}
ObjDate = new Date (ArrDate [0], ArrDate [1] - 1, ArrDate [2], ArrDate [3], ArrDate [4], ArrDate [5], ArrDate [6])
return ObjDate
}
// FUNCTION DoEuclid (a, b) THIS REQUIRES THE FUNCTION DoDivision (TheDividend, TheDivisor)******************
function DoEuclid (a, b) {
Euclid = new Array // This associative array will carry a and b, the HCF and LCM, and x and y so ax + by = HCF.
var nn = 0
var TheTemp = 0
var TheOrder = 0
a = Math.round (a) // Should be unnecessary.
b = Math.round (b)
if (Math.abs (a) > Math.abs (b)) { // Make sure that |a| <= |b|.
TheTemp = a
a = b
b = TheTemp
}
q = new Array // These are the successive quotients.
r = new Array (a, b) // r [2], r [3], ... are the successive remainders.
x = new Array // x and y are for tracking backwards. Eventually a * x[0] + b * y[0] = HCF.
y = new Array
Euclid.a = a
Euclid.b = b
// STEP 1: First deal with either or both integers zero, and with two integers equal in absolute value.
if (a == 0 || Math.abs (a) == Math.abs (b)) {
Euclid.HCF = Math.abs (b)
Euclid.LCM = Math.abs (a)
Euclid.x = 0
Euclid.y = 0
if (b > 0)
Euclid.y = 1
if (b < 0)
Euclid.y = -1
} else {
// STEP 2: Otherwise, carry out the Euclidean algorithm and record the successive quotients and remainders.
TheTemp = 0
nn = 0
while (!(r [nn + 1] == 0) && nn < 500) { // The condition nn < 500 is to protect from an infinite loop.
q [nn] = DoDivision (r [nn], r [nn + 1]).q
r [nn + 2] = DoDivision (r [nn], r [nn + 1]).r
DOpus.Output (r [nn] + " divided by " + r [nn + 1] + " = " + q [nn] + " remainder " + r [nn + 2]) // Delete after testing.
nn++
}
TheOrder = nn
Euclid.HCF = Math.abs (r [TheOrder])
Euclid.LCM = Math.abs (Math.round ((Euclid.a * Euclid.b) / Euclid.HCF))
// STEP 3: Then track backwards through the division steps to find integers x and y such that ax + by = HCF.
x [TheOrder - 2] = 1
y [TheOrder - 2] = - q [TheOrder - 2]
for (nn = TheOrder - 2; nn > 0; nn = nn - 1) {
x [nn - 1] = y [nn]
y [nn - 1] = x [nn] - (q [nn - 1] * y [nn])
}
Euclid.x = x [0]
Euclid.y = y [0]
} // Closes "if" in Step 1.
return Euclid
}[/code]
I would also like to refer to two excellent online JScript references that between them cleared up many difficulties I had (only readable after jon and tbone's advice and encouragement):
ns7.webmasters.com/caspdoc/html/ ... erence.htm
tergestesoft.com/~manuals/jslang/jstoc.htm