JIRA Time log automation

Log work in JIRA from slimtimer by reading task tags.

当前为 2015-11-02 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name JIRA Time log automation
  3. // @namespace com.spiderlogic
  4. // @description Log work in JIRA from slimtimer by reading task tags.
  5. // @include http*://*slimtimer.com/edit*
  6. // @include http*://*.jira.com/browse/*
  7. // @include http*://*.jira.com/secure/CreateWorklog!default.jspa*
  8. // @include http*://*/jira/browse/*
  9. // @include http*://*/jira/secure/CreateWorklog!default.jspa*
  10. //
  11. // @version 0.0.1.20151102124403
  12. // ==/UserScript==
  13. //Credits http://jacwright.com/projects/javascript/date_format
  14. /*
  15. Use this script to automatically log work in JIRA for tasks tracked in slimtimer.
  16. All you have to do is tag the tasks (and not time entries) in a specific pattern (JIRA@XXXX-NNN by default). With this done and after customizing variables mentioned below, you should be able to view links for each entry that can be logged on "Edit Entries" page of slimtimer website.
  17. First link <b>XXXX-NNN</B> is issue link - just for convinience, opens the work log tab panel by default.
  18. Second link <b>Auto Log</b> is to automatically log work in JIRA and tag the entry as Logged (customizable).
  19. Third link <b>Manual Log</b> is to manually log work in JIRA (will keep the log work window open without logging any work so that you can edit the details if necessary) and tag the entry as Logged (customizable).
  20.  
  21. The above links appear on the edit entries page for 'today'. If you navigate to any other date the link dissapear. To enable the links click <b>Enable JIRA</b> added to main menu of slimtimer (besides API menu).
  22.  
  23. You will have to customize following variables in the script for it to work.
  24.  
  25. 1. jiraWebSiteLink - link to your JIRA website: You can extract it from any of the JIRA issue links. e.g if your issue link is http://yourprogram.youwebsite.com/browse/XXX-NNNN, then this variabel should point to http://yourprogram.youwebsite.com.
  26. <B font='red'>This must be customized else the JIRA links will be invalid</B>
  27. 2. loggedTag - Tag with which the time entry will be updated automatically if Auto Log or Manual Log links are clicked.
  28. Default - Logged
  29.  
  30. 3. jiraPrefix - Prefix you would like to use in Slimtimer task tags.
  31. Default - JIRA@
  32.  
  33.  
  34. Issues:
  35. 1. All tags in time entry (not the task tags) are overwritten.
  36. 2. Work is logged at 10.00 AM in JIRA
  37.  
  38. Tested with JIRA Studio 2.2 and JIRA 3.13.3-#344
  39.  
  40. */
  41.  
  42. new function() {
  43.  
  44. //variables to be customised for each site
  45. var jiraWebSiteLink = "http://yourprogram.yourwebsite.com"
  46. var loggedTag = 'Logged'
  47. var jiraPrefix = 'JIRA@'
  48.  
  49. //my variables
  50. //variables to be customised for each site
  51. jiraWebSiteLink = "https://churchmutual.jira.com"
  52. //jiraWebSiteLink = "http://spiderlogic.jira.com"
  53. loggedTag = 'ttg-P.Planning and Tracking.Logged'
  54. jiraPrefix = 'JIRA@'
  55. jiraCommentPrefix = 'JC='
  56.  
  57. //dont change anything below unless you know what you are doing
  58.  
  59. var slimTimerWebsiteLinkRE = 'http*://*slimtimer.com/edit*'
  60. var jiraIssueLinkRE = ".*/browse/.*"
  61. var jiraWLLinkRE = ".*/secure/CreateWorklog!default.jspa.*"
  62.  
  63.  
  64.  
  65. var windowURL = window.location.href
  66.  
  67. var reURL = new RegExp(slimTimerWebsiteLinkRE)
  68. var bExists = reURL.exec(windowURL);
  69.  
  70. if(bExists!=null){
  71. window.addEventListener("load", initializeST , false);
  72. } else {
  73. reURL = new RegExp(jiraIssueLinkRE)
  74. bExists = reURL.exec(windowURL);
  75. if(bExists!=null){
  76. window.addEventListener("load", initializeJI , false);
  77. } else {
  78. reURL = new RegExp(jiraWLLinkRE)
  79. bExists = reURL.exec(windowURL);
  80. if(bExists!=null){
  81. window.addEventListener("load", initializeWL , false);
  82. }
  83. }
  84. }
  85.  
  86. //slimtimer.com
  87.  
  88. function initializeST(){
  89. showMenu();
  90. addLinks();
  91. }
  92.  
  93. function showMenu(){
  94.  
  95. var menu = document.evaluate( "//*[@id='nav']/ul", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
  96. menu=menu.snapshotItem(0);
  97. var div = document.createElement("li");
  98. var aLink = document.createElement("a");
  99. aLink.setAttribute("href","#")
  100. aLink.addEventListener('click', addLinks, true);
  101. aLink.innerHTML="Enable JIRA"
  102. div.appendChild(aLink);
  103. menu.appendChild(div)
  104. div = document.createElement("li");
  105. aLink = document.createElement("a");
  106. aLink.setAttribute("href","#")
  107. aLink.addEventListener('click', clickAllAutoLogLinks, true);
  108. aLink.innerHTML="Auto Log All"
  109. div.appendChild(aLink);
  110. menu.appendChild(div)
  111. var cal = document.evaluate( "//*[@id='edit-entries-header']", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
  112. cal=cal.snapshotItem(0);
  113. cal.addEventListener('load', addLinks, true);
  114. }
  115.  
  116. function addLinks(){
  117.  
  118. var dateToLog = document.evaluate( "//a[@id='current-date']", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
  119. dateToLog = dateToLog.snapshotItem(0).textContent;
  120. dateToLog = dateToLog.substring(3,dateToLog.length)
  121. //alert(dateToLog)
  122.  
  123. var day = dateToLog.substring(0,dateToLog.indexOf("/"));
  124. dateToLog= dateToLog.substring(day.length+1, dateToLog.length);
  125. //alert(dateToLog)
  126.  
  127. var month = dateToLog.substring(0 ,dateToLog.indexOf("/"));
  128. dateToLog= dateToLog.substring(month.length+1, dateToLog.length);
  129. //alert(dateToLog)
  130.  
  131. var year = dateToLog.substring(0 ,dateToLog.length);
  132. //alert(year)
  133.  
  134.  
  135. dateToLog=new Date(year,month-1,day);
  136. //alert(dateToLog)
  137.  
  138. dateToLog = dateToLog.format('d/M/Y')
  139.  
  140.  
  141. //alert(dateToLog)
  142.  
  143.  
  144.  
  145. findPattern = "//div[@class='time-entry-content']/table/tbody"
  146. var entryTBody = document.evaluate( findPattern, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
  147.  
  148. for ( var i=0 ; i < entryTBody.snapshotLength; i++ )
  149. {
  150. //alert( entryTBody.snapshotItem(i).textContent );
  151. var tBody = entryTBody.snapshotItem(i)
  152.  
  153.  
  154. var tags = document.evaluate( "tr/td[@class='column-1']/p[@class='tags']", tBody, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
  155. if (tags == null || tags.snapshotLength == 0 ) {
  156. continue;
  157. }
  158. tags=tags.snapshotItem(0);
  159. //alert(tags.innerHTML)
  160.  
  161. var jiraLinkHref = null;
  162. var jiraLinkTarget = null;
  163. {
  164. var re = new RegExp('\\b'+jiraPrefix+'[a-z,A-Z]*\\-[0-9]*')
  165. var m = re.exec(tags.textContent);
  166. if (m != null) {
  167. //alert(m)
  168. var s = "";
  169. for (j = 0; j < m.length; j++) {
  170. s = s + m[j] + "\n";
  171. }
  172. jiraLinkHref = s
  173. jiraLinkTarget = s
  174. jiraLinkHref = jiraLinkHref.replace(jiraPrefix, jiraWebSiteLink+'/browse/')
  175. getTagLink(tBody,tags,jiraLinkHref,s, dateToLog)
  176.  
  177. }
  178.  
  179. }
  180.  
  181.  
  182. {
  183.  
  184. var re = new RegExp('\\bhttp:.*\\d\\b')
  185. var m = re.exec(tags.textContent);
  186. if (m != null) {
  187. var s = "";
  188. for (j = 0; j < m.length; j++) {
  189. s = s + m[j] + "\n";
  190. }
  191. jiraLinkHref = s
  192. jiraLinkTarget = s
  193. getTagLink(tBody,tags,jiraLinkHref,s, dateToLog)
  194. }
  195. }
  196.  
  197.  
  198. }
  199.  
  200. }
  201.  
  202.  
  203. function getTagLink(tBody, tags, issueLink, issue, dateToLog){
  204.  
  205. var hiddenElement = document.evaluate( "*//*[@id='slJIRAInt']", tBody, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
  206. if(hiddenElement.snapshotLength>0){
  207. return;
  208. }
  209. hiddenElement = document.createElement("input");
  210. hiddenElement.type="hidden"
  211. hiddenElement.id = 'slJIRAInt'
  212. hiddenElement.setAttribute('issueLink',issueLink)
  213. hiddenElement.setAttribute('issueTagComment',createComment(tags))
  214. tags.appendChild(hiddenElement);
  215. var jiraIssueLink = createLink("#", issue.replace(jiraPrefix,""),"click", onIssueLinkClick )
  216. tags.appendChild(jiraIssueLink)
  217.  
  218. var jiraAutoLogLink = createLink("#", "Auto Log","click", onLogLinkClick )
  219. tags.appendChild(jiraAutoLogLink)
  220.  
  221. var jiraManualLogLink = createLink("#", "Manual Log","click", onLogLinkClick )
  222. tags.appendChild(jiraManualLogLink)
  223. setCommonParameters(hiddenElement, tBody, dateToLog)
  224.  
  225.  
  226. }
  227.  
  228. function createComment(tags){
  229. var tagComment = "";
  230. var re = new RegExp('\\b'+jiraCommentPrefix+'[^,]*,')
  231. var m = re.exec(tags.textContent);
  232. if (m != null) {
  233. //alert(m)
  234. var s = "";
  235. for (j = 0; j < m.length; j++) {
  236. s = s + m[j] + "\n";
  237. }
  238. tagComment = s
  239. tagComment = tagComment.replace(jiraCommentPrefix,"");
  240. tagComment = tagComment.replace(",","");
  241. }
  242. //alert(tagComment)
  243. return tagComment
  244. }
  245.  
  246. function createLink(href, innerHTML, event, functionReference){
  247. var aLink = document.createElement("A");
  248. aLink.href=href
  249. aLink.innerHTML = innerHTML
  250. aLink.addEventListener(event, functionReference, true)
  251. return aLink;
  252. }
  253.  
  254. function gethiddenElement(aLink){
  255. return document.evaluate( "input[@id='slJIRAInt']", aLink.parentNode, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null ).snapshotItem(0);
  256. }
  257.  
  258. function onIssueLinkClick(){
  259. var hiddenElement = gethiddenElement(this)
  260. var link = hiddenElement.getAttribute("issueLink")+"?page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel#issue-tabs"
  261. window.target= link
  262. window.open(link);
  263. }
  264.  
  265. function onLogLinkClick(){
  266. var hiddenElement = gethiddenElement(this)
  267. window.open(hiddenElement.getAttribute('saveTagLink'))
  268. var link = hiddenElement.getAttribute("link")
  269. alert(link+"&logWork="+this.innerHTML.replace(" Log",""));
  270. window.target= link
  271. window.open(link+"&logWork="+this.innerHTML.replace(" Log",""));
  272.  
  273. }
  274.  
  275.  
  276. function setCommonParameters(hiddenElement, tBody, dateToLog){
  277. var duration = document.evaluate( "tr/td[@class='duration']", tBody, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
  278. duration=duration.snapshotItem(0).textContent;
  279. duration= duration.replace(":","h!").replace(new RegExp("[.].*","g"),"m").trim()
  280. var comment = document.evaluate( "tr/td[@class='comments']/p", tBody, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
  281. if(comment!=null && (comment=comment.snapshotItem(0))!=null){
  282. comment=comment.textContent;
  283. //alert(comment)
  284. } else {
  285. comment=hiddenElement.getAttribute('issueTagComment');
  286. //alert(comment);
  287. }
  288. var timeRange = document.evaluate( "tr/td[@class='time-range']", tBody, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
  289. timeRange=timeRange.snapshotItem(0).textContent;
  290. var task = document.evaluate( "*//p[@class='task']", tBody, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
  291. task=task.snapshotItem(0).textContent;
  292. var startTime = timeRange.substring(0,timeRange.indexOf('-')).trim()
  293. var endTime = timeRange.substring(timeRange.indexOf('-')+1, timeRange.length).trim()
  294. var jiraStartTime = startTime;
  295. var jiraStartTimeSuffix = "";
  296. if(jiraStartTime.indexOf('M')>=0){
  297. jiraStartTime = startTime.substring(0,startTime.length-2).trim();
  298. jiraStartTimeSuffix = startTime.substring(startTime.length-2,startTime.length).trim();
  299. } else {
  300. var jiraStartTimeHour = startTime.substring(0,startTime.indexOf(':')).trim();
  301. var jiraStartTimeMin = startTime.substring(startTime.indexOf(':')+1,startTime.length).trim();
  302. if(jiraStartTimeHour != 12 && jiraStartTimeHour !=0){
  303. jiraStartTime = jiraStartTimeHour % 12 + ":" + jiraStartTimeMin
  304. } else {
  305. jiraStartTime = 12 + ":" + jiraStartTimeMin
  306. }
  307. //alert(jiraStartTimeHour/12%2)
  308. if(jiraStartTimeHour/12%2 < 1.0){
  309. jiraStartTimeSuffix = "AM";
  310. } else {
  311. jiraStartTimeSuffix = "PM";
  312. }
  313. }
  314. //alert(duration)
  315. if(duration == "0m"){
  316. hiddenElement.setAttribute('link' , "javascript:alert('Nothing to log (0m cannot be logged)')");
  317. } else {
  318. hiddenElement.setAttribute ('link', hiddenElement.getAttribute("issueLink")+
  319. "?duration="+duration+"&dateToLog="+dateToLog+"!"+jiraStartTime+"!"+jiraStartTimeSuffix+"&workDescription="+comment);
  320. }
  321.  
  322. var entryId = tBody.parentNode.parentNode.parentNode.id
  323. entryId = entryId.substring('time_entry-view-'.length, entryId.length-'-row'.length)
  324. var saveTagLink = '/edit/update/'+entryId +'?scaffold_id=time_entry'
  325. saveTagLink+='&duration-field-'+entryId +'='+duration
  326. saveTagLink+='&tag_input_'+entryId +'='+loggedTag
  327. saveTagLink+='&time_entry[tags]='+loggedTag
  328. saveTagLink+='&task_field_'+entryId +'='+task
  329. saveTagLink+='&time_entry[comments]='+comment
  330. saveTagLink+='&start-time-field-'+entryId +'='+startTime
  331. saveTagLink+='&end-time-field-'+entryId +'='+endTime
  332. //alert(saveTagLink)
  333. hiddenElement.setAttribute('saveTagLink',saveTagLink);
  334.  
  335. }
  336.  
  337. //Jira Issue
  338.  
  339.  
  340. function initializeJI(){
  341. var logWork = gup('logWork')
  342. //alert(logWork)
  343. if(logWork == "Auto" || logWork == "Manual"){
  344. //alert(1)
  345. //try JIRa studio 2.2
  346. var logWorkLink = document.evaluate( "//a[@id='log-work'] | //a[@id='log_work']", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
  347. logWorkLink=logWorkLink.snapshotItem(0);
  348. //alert(logWorkLink)
  349. logWorkLink = logWorkLink + (new RegExp("[?].*").exec(windowURL)+"").replace("?","&");
  350. //alert(logWorkLink)
  351. window.location.href=logWorkLink
  352. }
  353.  
  354. }
  355.  
  356.  
  357.  
  358. //JIra work log
  359.  
  360. function initializeWL(){
  361. var logWork = gup('logWork')
  362. //alert(logWork)
  363. if(logWork == "Auto" || logWork == "Manual"){
  364. var forSpaces = new RegExp("!","g");
  365. var duration = gup('duration').replace(forSpaces," ");
  366. var timeLogged = document.evaluate( "//input[@name='timeLogged']", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
  367. var timeLogged = timeLogged.snapshotItem(0);
  368. timeLogged.value=duration;
  369. var dateToLog = gup('dateToLog').replace(forSpaces," ");;
  370. var startDate = document.evaluate( "//input[@name='startDate']", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
  371. var startDate = startDate.snapshotItem(0);
  372. startDate.value=dateToLog;
  373. var workDescription = gup('workDescription').replace(forSpaces," ");;
  374. var comment = document.evaluate( "//textarea[@name='comment']", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
  375. var comment = comment.snapshotItem(0);
  376. comment.innerHTML=workDescription;
  377. if(logWork == "Auto"){
  378. var logButton = document.evaluate( "//input[@name='Log']", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
  379. var logButton = logButton.snapshotItem(0);
  380. //alert(logButton)
  381. logButton.click();
  382. //window.close();
  383. }
  384. }
  385.  
  386. }
  387.  
  388. function clickAllAutoLogLinks(){
  389. addLinks();
  390. var tags = document.evaluate( "//div[@class='time-entry-content']/table/tbody/tr/td[@class='column-1']/p[@class='tags']",
  391. document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
  392. for ( var i=0 ; i < tags.snapshotLength; i++ ){
  393. var aTag = tags.snapshotItem(i)
  394. var aLink = document.evaluate("a[normalize-space(.)='Auto Log']", aTag, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
  395. if (aLink == null || aLink.snapshotLength==0){
  396. aTag.style.color="red";
  397. continue;
  398. }
  399. aLink = aLink.snapshotItem(0)
  400. if(aTag.textContent.indexOf(loggedTag)<0){
  401. //alert("clicking \n" + aTag.innerHTML + "\n " + loggedTag )
  402. aLink.click();
  403. } else {
  404. aLink.style.color="red";
  405. //alert("skipping \n" + aTag.innerHTML + "\n " + loggedTag )
  406. }
  407. }
  408. alert("Refresh?")
  409. window.location.replace("http://slimtimer.com/edit");
  410.  
  411. }
  412.  
  413. //common***************************************************
  414.  
  415. function gup( name )
  416. {
  417. //alert(name)
  418. name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
  419. var regexS = "[\\?&]"+name+"=([^&#]*)";
  420. var regex = new RegExp( regexS );
  421. var results = regex.exec( windowURL );
  422. if( results == null ){
  423. return "";
  424. }
  425. else{
  426. return unescape(results[1]);
  427. }
  428. }
  429.  
  430.  
  431. String.prototype.trim = function () {
  432. return this.replace(/^\s*/, "").replace(/\s*$/, "");
  433. }
  434.  
  435.  
  436. Date.prototype.format = function(format) {
  437. var returnStr = '';
  438. var replace = Date.replaceChars;
  439. for (var i = 0; i < format.length; i++) {
  440. var curChar = format.charAt(i);
  441. if (replace[curChar]) {
  442. returnStr += replace[curChar].call(this);
  443. } else {
  444. returnStr += curChar;
  445. }
  446. }
  447. return returnStr;
  448. };
  449. Date.replaceChars = {
  450. shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
  451. longMonths: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
  452. shortDays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
  453. longDays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
  454. // Day
  455. d: function() { return (this.getDate() < 10 ? '0' : '') + this.getDate(); },
  456. D: function() { return Date.replaceChars.shortDays[this.getDay()]; },
  457. j: function() { return this.getDate(); },
  458. l: function() { return Date.replaceChars.longDays[this.getDay()]; },
  459. N: function() { return this.getDay() + 1; },
  460. S: function() { return (this.getDate() % 10 == 1 && this.getDate() != 11 ? 'st' : (this.getDate() % 10 == 2 && this.getDate() != 12 ? 'nd' : (this.getDate() % 10 == 3 && this.getDate() != 13 ? 'rd' : 'th'))); },
  461. w: function() { return this.getDay(); },
  462. z: function() { return "Not Yet Supported"; },
  463. // Week
  464. W: function() { return "Not Yet Supported"; },
  465. // Month
  466. F: function() { return Date.replaceChars.longMonths[this.getMonth()]; },
  467. m: function() { return (this.getMonth() < 9 ? '0' : '') + (this.getMonth() + 1); },
  468. M: function() { return Date.replaceChars.shortMonths[this.getMonth()]; },
  469. n: function() { return this.getMonth() + 1; },
  470. t: function() { return "Not Yet Supported"; },
  471. // Year
  472. L: function() { return (((this.getFullYear()%4==0)&&(this.getFullYear()%100 != 0)) || (this.getFullYear()%400==0)) ? '1' : '0'; },
  473. o: function() { return "Not Supported"; },
  474. Y: function() { return this.getFullYear(); },
  475. y: function() { return ('' + this.getFullYear()).substr(2); },
  476. // Time
  477. a: function() { return this.getHours() < 12 ? 'am' : 'pm'; },
  478. A: function() { return this.getHours() < 12 ? 'AM' : 'PM'; },
  479. B: function() { return "Not Yet Supported"; },
  480. g: function() { return this.getHours() % 12 || 12; },
  481. G: function() { return this.getHours(); },
  482. h: function() { return ((this.getHours() % 12 || 12) < 10 ? '0' : '') + (this.getHours() % 12 || 12); },
  483. H: function() { return (this.getHours() < 10 ? '0' : '') + this.getHours(); },
  484. i: function() { return (this.getMinutes() < 10 ? '0' : '') + this.getMinutes(); },
  485. s: function() { return (this.getSeconds() < 10 ? '0' : '') + this.getSeconds(); },
  486. // Timezone
  487. e: function() { return "Not Yet Supported"; },
  488. I: function() { return "Not Supported"; },
  489. O: function() { return (-this.getTimezoneOffset() < 0 ? '-' : '+') + (Math.abs(this.getTimezoneOffset() / 60) < 10 ? '0' : '') + (Math.abs(this.getTimezoneOffset() / 60)) + '00'; },
  490. P: function() { return (-this.getTimezoneOffset() < 0 ? '-' : '+') + (Math.abs(this.getTimezoneOffset() / 60) < 10 ? '0' : '') + (Math.abs(this.getTimezoneOffset() / 60)) + ':' + (Math.abs(this.getTimezoneOffset() % 60) < 10 ? '0' : '') + (Math.abs(this.getTimezoneOffset() % 60)); },
  491. T: function() { var m = this.getMonth(); this.setMonth(0); var result = this.toTimeString().replace(/^.+ \(?([^\)]+)\)?$/, '$1'); this.setMonth(m); return result;},
  492. Z: function() { return -this.getTimezoneOffset() * 60; },
  493. // Full Date/Time
  494. c: function() { return this.format("Y-m-d") + "T" + this.format("H:i:sP"); },
  495. r: function() { return this.toString(); },
  496. U: function() { return this.getTime() / 1000; }
  497. };
  498.  
  499. }