JIRA Time Log Automation

Log work in JIRA from xls or slimtimer by reading task tags.

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