var ATime = Class.create({//time duration hh: 00, mm: 00, ss: 00, hhPadded: '00', mmPadded: '00', ssPadded: '00', initialize: function(hh, mm, ss){ this.hh=parseInt(hh, 10); this.mm=parseInt(mm, 10); if (typeof(ss) != "undefined") this.ss=parseInt(ss, 10); if (!this.IsIncorrect()) { this.hhPadded=this.hh.toPaddedString(2); this.mmPadded=this.mm.toPaddedString(2); this.ssPadded=this.ss.toPaddedString(2); } }, toString: function(){ if (this.IsIncorrect()) return ATime.notATime; return this.hhPadded+":"+this.mmPadded+":"+this.ssPadded; }, IsIncorrect: function() { return isNaN(this.hh) || isNaN(this.mm) || isNaN(this.ss); }, ToISOpart: function(){ if (this.IsIncorrect()) return ATime.notATime; return this.hhPadded+this.mmPadded+this.ssPadded; }, fromISOpart: function(str){ this.hhPadded=str.substr(0, 2); this.mmPadded=str.substr(2, 2); this.hh=parseInt(this.hhPadded, 10); this.mm=parseInt(this.mmPadded, 10); if (str.length <= 4) { this.ss=0; this.ssPadded='00'; return; } this.ssPadded=str.substr(4, 2); this.ss=parseInt(this.ssPadded, 10); }, FromISO: function(str) { this.fromISOpart(str.substr(str.indexOf("T") + 1)); }, Serialize: function(serializer){ serializer.BeginType("ATime"); serializer.Serialize(this.ToISOpart()); serializer.EndType("ATime"); }, Restore: function(serializer) { serializer.BeginType("ATime"); var str = serializer.Restore(); serializer.EndType("ATime"); if (str == ATime.notATime){ this.hh = NaN; this.mm = NaN; this.ss = NaN; } else this.fromISOpart(str); }, GetHours: function(){ return this.hh; }, GetMinutes: function(){ return this.mm; }, GetSeconds: function(){ return this.ss; } }); ATime.notATime = "not-a-date-time"; ATime.Current = function(){ var time = new Date(); return new ATime(time.getHours(), time.getMinutes(), time.getSeconds()); } var TimePoint = Class.create(ATime, { //timezone_offset should be undefined (interpreted as local) //or equal to timezone offset (i.e. - ) in minutes initialize: function(hh, mm, ss, timezone_offset){ this.hh = Number(hh); this.mm = Number(mm); if (typeof ss != "undefined") this.ss = Number(ss); this.timezone_offset_ = typeof timezone_offset == "undefined" ? TimePoint.GetLocalTimezoneOffset() : timezone_offset; }, toString: function(){ if (this.IsIncorrect()) return ATime.notATime; var offs = Math.abs(this.timezone_offset_); return [this.hh, this.mm, this.ss].invoke("toPaddedString", 2).join(":") + // (this.timezone_offset_ >= 0 ? "+" : "-") + [Math.floor(offs / 60), offs % 60].invoke("toPaddedString", 2).join(":"); }, IsIncorrect: function(){ return isNaN(this.hh) || isNaN(this.mm) || isNaN(this.ss) || typeof this.timezone_offset_ == "undefined"; }, ToISOpart: function(){ if (this.IsIncorrect()) return ATime.notATime; var result = [this.hh, this.mm, this.ss].invoke("toPaddedString", 2).join(""); if (typeof this.timezone_offset_ == "number"){ var offs = Math.abs(this.timezone_offset_); result += (this.timezone_offset_ >= 0 ? "+" : "-") + [Math.floor(offs / 60), offs % 60].invoke("toPaddedString", 2).join(""); } return result; }, fromISOpart: function(str){ this.hhPadded=str.substr(0, 2); this.hh=parseInt(this.hhPadded, 10); this.mmPadded=str.substr(2, 2); this.mm=parseInt(this.mmPadded, 10); this.timezone_offset_=0; if (str.length <= 4) { this.ssPadded='00'; this.ss=0; return; } this.ssPadded=str.substr(4, 2); this.ss=parseInt(this.ssPadded, 10); if (str.length <= 6) return; if (str.length == 7 && str.endsWith("Z")) this.timezone_offset_ = 0; else{ var offs = str.substr(6); var sign = str[0]; if (sign != "+" && sign != "-") return; var offs_hh = parseInt(offs.substr(1, 2), 10); var offs_mm = parseInt(offs.substr(3, 2), 10); this.timezone_offset_ = offs_hh * 60 + offs_mm; if (sign == "-") this.timezone_offset_ *= -1; } }, Serialize: function(serializer){ serializer.BeginType("TimePoint"); serializer.Serialize(this.ToISOpart()); serializer.EndType("TimePoint"); }, Restore: function(serializer) { serializer.BeginType("TimePoint"); var str = serializer.Restore(); serializer.EndType("TimePoint"); if (str == ATime.notATime){ this.hh = NaN; this.mm = NaN; this.ss = NaN; }else this.fromISOpart(str); }, ToLocalTime: function() { if (this.IsIncorrect()) return new ATime(); var hhmm = this.hh * 60 + this.mm - this.GetTimezoneOffset() + TimePoint.GetLocalTimezoneOffset(); if (hhmm >= 0) hhmm = hhmm % (24 * 60); else while (hhmm < 0) hhmm += 24 * 60; return new ATime(Math.floor(hhmm / 60), hhmm % 60, this.ss); }, GetTimezoneOffset: function(){ return this.timezone_offset_ || 0; } }); TimePoint.GetLocalTimezoneOffset = function(){ return -(new Date()).getTimezoneOffset(); } var TimeInput = Class.create(SuperclassInput, { initialize: function($super, element, params){ params = params || {}; $super(element, params.button); if (params["attrs"]){ this.opener = new SIPopupOpener(params.button, { "html": function(){ return JSControl.TransformElementAndUnwrap(params["attrs"], params.types || {}); }, "tablimit": 16, "align-id": this.ID() }); SubscribeOnEvent("CHANGE", "document", this.onCHANGE, this); SubscribeOnEvent("FINALIZE", "document", this.onFINALIZE, this); Event.observe(params.button, "SI:SHOW", function(event){ if (event.memo != this.opener) return; var clock = $(params["attrs"].htmlid); if (!clock || !clock.JSControl) return; clock.JSControl.SetValue(this.Value()); }.bindAsEventListener(this)); } this.params = params; this.value_class_ = this.element.getAttribute("time-value-type") == "point" ? TimePoint : ATime this.delimiter = this.element.readAttribute("time-delimiter"); if (/\d/.test(this.delimiter)) LogE("Time delimiter MUST NOT contain digits"); var delimStr = { "complete": SuperclassInput.addBackslashForRegexp(this.delimiter), "incomplete": SuperclassInput.constructPartialMatch(this.delimiter), "nonEmpty": SuperclassInput.constructPartialMatch(this.delimiter, false) }; this.limits = { "h": {"min": 0, "max": 23, "errMin": "`HOUR < 0`", "errMax": "`HOUR > 23`"}, "m": {"min": 0, "max": 59, "errMin": "`MINUTES < 0`", "errMax": "`MINUTES > 59`"}, "s": {"min": 0, "max": 59, "errMin": "`SECONDS < 0`", "errMax": "`SECONDS > 59`"} }; this.clockFormat = Element.readAttribute(this.element, "clock-format"); if (this.clockFormat == "AMPM"){ Object.extend(this.limits.h, {"min": 1, "errMin": "`HOUR < 1`", "max": 12, "errMax": "`HOUR > 12`"}); } this.format = this.element.readAttribute("time-format"); switch (this.format){ case "HHMMSS": this.timeFormat = "hms"; break; case "HHMM": this.timeFormat = "hm"; break; default: LogE("Incorrect time format: " + this.format); return; }; var timeFormatArray = this.timeFormat.split(""); this.formatToDisplay = timeFormatArray.collect(function(char){ return TimeInput.formatNames[char]; }).join(this.delimiter) + (this.clockFormat == "AMPM" ? " AM|PM" : ""); this.incorrectFormatMessage = TimeInput.incorrectformat + " (" + this.formatToDisplay + ")"; this.timeComponents = timeFormatArray.collect(function(char, iii){ return {"type": char, "complete": "\\d{2}", "incomplete": "\\d{1,2}", "index": iii, "length": 2}; }); this.timeComponents.each(function(timeComponent){ this.timeComponents[timeComponent.type] = timeComponent; }.bind(this)); var completeMatch = ""; var lineend = (this.clockFormat == "AMPM" ? SuperclassInput.constructPartialMatchFromArray(["\\s+", "([AP]M?)"]) : "") + "$"; var syntaxFullComponents = this.timeComponents.collect( function(component){ return component.complete; }).join(delimStr.complete); this.syntaxFull = new RegExp("^" + syntaxFullComponents + (this.clockFormat == "AMPM" ? "\\s+[AP]M" : "") + "$", "i"); this.syntax = new RegExp("^$|" + this.timeComponents.collect(function(component, iii){ var result = completeMatch + component.incomplete; if (iii < this.timeComponents.length - 1){ result = completeMatch + component.complete + delimStr.incomplete + "$|^" + result; completeMatch += component.complete + delimStr.complete; } return "^" + result + "$"; }.bind(this)).join("|") + "|^" + syntaxFullComponents + lineend, "i"); this.semantics = new RegExp("^" + SuperclassInput.constructPartialMatchFromArray( this.timeComponents.collect(function(component, iii){ var result = ["(" + component.incomplete + ")"]; if (iii > 0) result.unshift(delimStr.nonEmpty); return result; }).flatten()) + lineend, "i"); if (this.clockFormat == "AMPM") this.timeComponentsExt = this.timeComponents.concat({ "type": "ampm", "complete": "[AP]M", "index": this.timeComponents.length, "length": 2 }); else this.timeComponentsExt = this.timeComponents; this.completeInitialization(); }, checkSemantics: function(str, silent, fullCheck){ var matchedArray = this.semantics.exec(str); if (!matchedArray){ this.setErrorMessage(this.incorrectFormatMessage, silent); return false; } matchedArray.shift(); while (matchedArray.length > 0 && typeof matchedArray[matchedArray.length - 1] == "undefined") --matchedArray.length; if (!(matchedArray.collect(function(unit, iii){ if (this.timeComponentsExt[iii].type == "ampm") return true; var number = Number(unit); var isComplete = fullCheck || RegExp("^" + this.timeComponents[iii].complete + "$").test(unit); var componentType = this.timeComponents[iii].type; var min = this.limits[componentType].min; if (isComplete && !isNaN(min) && number < min){ this.setErrorMessage(this.limits[componentType].errMin, silent); return false; } var max = this.limits[componentType].max; if (!isNaN(max) && number > max){ this.setErrorMessage(this.limits[componentType].errMax, silent); return false; } return true; }.bind(this)).all())) return false; return true; }, getTimeComponent: function(str, pos){ var currentPos = 0; var startPos = 0; var delimiterLength = this.delimiter.length; var isIn = new Array(this.timeComponents.length); this.timeComponents.each(function(component, iii){ isIn[iii] = pos >= currentPos && pos < currentPos + component.length; currentPos += component.length + delimiterLength; }); var iii = isIn.indexOf(true); return iii == -1 ? "" : this.timeComponents[iii].type; }, isFirstCharInComponent: function(str, pos){ return pos == 0 || str.charAt(pos - 1) == this.delimiter.charAt(this.delimiter.length - 1); }, preModify: function(str, newch, ss, se){ var result = {"str": str, "ss": ss, "se": se}; if (newch.length == 0 || this.delimiter.length == 0 || !this.check(str, true) || // this.check(this.getModifiedStr(str, newch, ss, se), true)) return result; var lastDelimiterChar = this.delimiter.charAt(this.delimiter.length - 1); switch (newch.toUpperCase()){ case this.delimiter.charAt(0): if (ss == 0 || se < str.length || !str.charAt(ss - 1).isDigit()) return result; str = str.substr(0, ss); var componentStart = str.lastIndexOf(lastDelimiterChar) + 1; var strModified = this.getModifiedStr(str, "0", componentStart); if (!this.check(strModified, true)) return result; result.str = strModified; ++result.ss; ++result.se; break; case "A": case "P": case " ": if (this.clockFormat != "AMPM" || ss == 0 || se < str.length) break; str = str.substr(0, ss); var component = this.getTimeComponent(str, ss); if (!component) break; var componentStart = str.lastIndexOf(lastDelimiterChar) + 1; var componentIndex = this.timeComponents[component].index; var strModified = this.getModifiedStr(str, "0".times(componentStart + this.timeComponents[component].length - ss), componentStart) + this.timeComponents.collect(function(component, iii){ return iii > componentIndex ? this.delimiter + "0".times(component.length) : ""; }.bind(this)).join(""); if (newch != " ") strModified += " "; if (!this.check(strModified, true)) break; result.str = strModified; result.ss = result.se = strModified.length; break; default: if (!newch.isDigit() || se >= str.length || se != ss + 1 || // this.getTimeComponent(str, ss) != "h") break; var strModified = this.isFirstCharInComponent(str, ss) ? // this.getModifiedStr(str, newch + "0", ss, se + 1) : // this.getModifiedStr(str, "0" + newch, ss - 1, se); if (this.check(strModified, true)) result.str = strModified; break; } return result; }, getValue: function(str){ if (typeof str == "undefined") str = this.element.value; var matchedArray = this.semantics.exec(str); var result = {}; this.timeComponents.each(function(component, iii){ result[component.type] = parseInt(matchedArray[iii + 1], 10); }); if (this.clockFormat == "AMPM"){ var ampm = matchedArray.pop(); if (typeof ampm == "undefined" || !ampm.match(new RegExp("^" + this.timeComponentsExt.last()["complete"] + "$", "i"))) return result; result["h"] = TimeInput.to24H({ "period": ampm , "value": result["h"] }); } return result; }, autoModify: function(str, ss, se){ var val = this.getValue(str); var delimiterIndices = str.indicesOf(this.delimiter); var thisobj = this; var limitsForAutoComplete = {"h": 2, "m": 5, "s": 5}; var is_ampm = this.clockFormat == "AMPM"; if (is_ampm) limitsForAutoComplete["h"] = 1; $H(limitsForAutoComplete).keys().each(function(char){ if (Object.isUndefined(val[char]) || isNaN(val[char]) || String(val[char]).length != 1 || Number(val[char]) <= limitsForAutoComplete[char]) return; var index = thisobj.timeComponents[char].index; var insertPoint = index == 0 ? 0 : delimiterIndices[index - 1] + thisobj.delimiter.length; var strModified = thisobj.getModifiedStr(str, "0", insertPoint); if (thisobj.check(strModified, true)){ str = strModified; ss += insertPoint <= ss; se += insertPoint <= se; for (var iii = index; iii < delimiterIndices.length; ++iii) ++delimiterIndices[iii]; } }); if (ss == str.length){ var to_check = [true, is_ampm, is_ampm]; var to_append = [this.delimiter, " ", "M"].find(function(chars, iii){ return to_check[iii] && this.check(str + chars, true); }, this); if (!Object.isUndefined(to_append)){ str += to_append; se = ss += to_append.length; } if (is_ampm && this.check(str, true)){ while (str.endsWith(" ")){ str = str.substr(0, str.length - 1); se = ss = str.length; } } } if (is_ampm) str = str.toUpperCase(); return {"str": str, "ss": ss, "se": se}; }, Value: function(){ if (!this.hintIsVisible() && this.check(this.element.value, true, true)){ var val = this.getValue(); if (this.format == "HHMM") val.s = 0; return new this.value_class_(val.h, val.m, val.s); } return new this.value_class_(NaN, NaN, NaN); }, SetValue: function($super, time){ if (typeof time == "undefined" || time === null) return void this.clear(); var fireWMInit=false; if (time == "current"){ var curDate=new Date(); time = new this.value_class_(curDate.getHours(), curDate.getMinutes(), curDate.getSeconds()); fireWMInit=true; } if (typeof time.ToLocalTime == "function") { time=time.ToLocalTime(); } if (time.IsIncorrect()) return void this.clear(); this.hideHint(); var suffix = ""; if (this.clockFormat == "AMPM"){ var ampm = TimeInput.toAMPM(time["hh"]); suffix = " " + ampm["period"]; time["hh"] = ampm["value"]; } this.element.value = this.timeComponents.collect(function(component){ return time[component.type.times(2)].toPaddedString(2); }).join(this.delimiter) + suffix; $super(time); if (fireWMInit) new AEvent("WM_INIT", {}, this); }, widgetID: function(){ return this.params["attrs"].htmlid; }, onCHANGE: function(aevent){ var emitter = aevent.Emitter(); if (emitter.ID() != this.widgetID()) return; this.SetValue(emitter.Value()); this.onchange(); }, onFINALIZE: function(aevent){ var emitter = aevent.Emitter(); if (emitter.ID() != this.widgetID()) return; if (this.opener) this.opener.Hide(); }, setCurrent: function(event){ var cur = ATime.Current(); if (cur.toString() != this.Value().toString()){ this.SetValue(cur); var widget = $(this.widgetID()); if (widget && widget.JSControl) widget.JSControl.SetValue(cur); this.onchange(); } }, SetAttribute: function($super, nam, val){ switch (nam){ case "visibility": $super(nam, val); if (this.opener) this.opener.Hide(); break; default: return $super(nam, val); } }, SetTabOrder: function($super, tabbase){ tabbase = $super(tabbase); if (this.opener) return this.opener.SetTabOrder(tabbase); else return tabbase; } }); TimeInput.formatNames = {"h": "`HH`", "m": "`MM`", "s": "`SS`"}; TimeInput.incorrectformat = "`Incorrect time format`"; TimeInput.toAMPM = function(val){ var result = { "period": val < 12 ? "AM" : "PM" }; val = val % 12; if (val == 0) val = 12; result["value"] = val; return result; } TimeInput.to24H = function(struct){ var val = struct["value"]; if (Object.isUndefined(val)) return 0; switch (String(struct["period"]).toUpperCase()){ case "AM": return (val == 12) ? 0 : val; case "PM": return val < 12 ? val + 12 : 12; } return val; }