Swatch Internet Time

Convert times to Swatch Internet Time .beats

  1. // ==UserScript==
  2. // @name Swatch Internet Time
  3. // @author Nik Rolls
  4. // @description Convert times to Swatch Internet Time .beats
  5. // @match *://*/*
  6. // @require https://greasyfork.org/scripts/395037-monkeyconfig-modern/code/MonkeyConfig%20Modern.js?version=764968
  7. // @grant GM_getMetadata
  8. // @grant GM_getValue
  9. // @grant GM_setValue
  10. // @grant GM_registerMenuCommand
  11. // @grant GM_addStyle
  12. // @namespace https://greasyfork.org/users/503103
  13. // @version 0.0.1.20200412044545
  14. // ==/UserScript==
  15.  
  16. (function() {
  17. 'use strict';
  18.  
  19. const cfg = new MonkeyConfig({
  20. menuCommand: true,
  21. params: {
  22. global_timezone: {
  23. label: 'Use global BMT timezone',
  24. type: 'checkbox',
  25. default: true
  26. }
  27. }
  28. });
  29.  
  30. let interval = null;
  31.  
  32. document.addEventListener('visibilitychange', detectVisibility);
  33. detectVisibility();
  34.  
  35. function detectVisibility() {
  36. if (document.visibilityState === 'visible') {
  37. startWatching();
  38. } else {
  39. stopWatching();
  40. }
  41. }
  42.  
  43. function startWatching() {
  44. augmentAllTimes();
  45. interval = window.setInterval(augmentAllTimes, 1000);
  46. }
  47.  
  48. function stopWatching() {
  49. if (interval) {
  50. window.clearInterval(interval);
  51. }
  52. }
  53.  
  54. function augmentAllTimes() {
  55. do {} while (augmentTimes())
  56. }
  57.  
  58. function augmentTimes() {
  59. let replacements = 0;
  60. const items = document.evaluate('/html/body//*[not(self::style or self::script or self::time or self::input or self::textarea or boolean(@contenteditable)) and text()[contains(.,":0") or contains(.,":1") or contains(.,":2") or contains(.,":3") or contains(.,":4") or contains(.,":5") or contains(.,":6") or contains(.,":7") or contains(.,":8") or contains(.,":9") or contains(.,"0pm") or contains(.,"1pm") or contains(.,"2pm") or contains(.,"3pm") or contains(.,"4pm") or contains(.,"5pm") or contains(.,"6pm") or contains(.,"7pm") or contains(.,"8pm") or contains(.,"9pm") or contains(.,"0 pm") or contains(.,"1 pm") or contains(.,"2 pm") or contains(.,"3 pm") or contains(.,"4 pm") or contains(.,"5 pm") or contains(.,"6 pm") or contains(.,"7 pm") or contains(.,"8 pm") or contains(.,"9 pm") or contains(.,"0am") or contains(.,"1am") or contains(.,"2am") or contains(.,"3am") or contains(.,"4am") or contains(.,"5am") or contains(.,"6am") or contains(.,"7am") or contains(.,"8am") or contains(.,"9am") or contains(.,"0 am") or contains(.,"1 am") or contains(.,"2 am") or contains(.,"3 am") or contains(.,"4 am") or contains(.,"5 am") or contains(.,"6 am") or contains(.,"7 am") or contains(.,"8 am") or contains(.,"9 am")]]', document.body, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
  61.  
  62. for (var i = 0; i < items.snapshotLength; i++) {
  63. const item = items.snapshotItem(i);
  64. const pattern = /(?:(?:([1-9][0-9]?(?::\d{2})*)\s*(a\.?m\.?|p\.?m\.?))|(\d{1,2}?:\d{2}(?::\d{2}(?:\.\d+)?)?))/i;
  65. const match = item.innerText.match(pattern);
  66. if (match) {
  67. let dateTime;
  68.  
  69. if (match[1]) {
  70. const time = match[1];
  71. const meridiem = match[2].replace(/\./g, '');
  72. dateTime = createDateTime(time, meridiem);
  73. } else if (match[3]) {
  74. dateTime = createDateTime(match[3]);
  75. }
  76.  
  77. if (dateTime) {
  78. const internetTime = convertToInternetTime(dateTime);
  79. const textNodes = Array.from(item.childNodes).filter(node => node.nodeType === Node.TEXT_NODE);
  80. textNodes.forEach((textNode) => {
  81. const textParts = textNode.textContent.match(new RegExp(`(.*)(${match[0]})(.*)`));
  82. if (textParts) {
  83. const newParts = [];
  84.  
  85. if (textParts[1]) {
  86. newParts.push(document.createTextNode(textParts[1]));
  87. }
  88.  
  89. if (textParts[2]) {
  90. const timeNode = document.createElement('time');
  91. timeNode.dateTime = dateTime.toISOString().replace(/^.*T|\+.*$|Z.*$/g, '');
  92. timeNode.title = match[0];
  93. timeNode.textContent = '@' + internetTime;
  94. newParts.push(timeNode);
  95. }
  96.  
  97. if (textParts[3]) {
  98. newParts.push(document.createTextNode(textParts[3]));
  99. }
  100. newParts.forEach(part => item.insertBefore(part, textNode));
  101. textNode.remove();
  102. replacements++;
  103. }
  104. });
  105. }
  106. }
  107. }
  108.  
  109. return replacements;
  110. }
  111.  
  112. function createDateTime(time, meridiem) {
  113. const timeParts = time.match(/\d+/g);
  114. const dateTime = new Date();
  115. dateTime.setHours(meridiem && timeParts[0] == 12 ? 0 : timeParts[0], timeParts[1] || 0, timeParts[2] || 0, timeParts[3] || 0);
  116. if (meridiem && meridiem.startsWith('p')) {
  117. dateTime.setHours(dateTime.getHours() + 12);
  118. }
  119. return dateTime;
  120. }
  121.  
  122. function convertToInternetTime(dateTime) {
  123. dateTime = new Date(dateTime.getTime());
  124.  
  125. if (cfg.get('global_timezone')) {
  126. dateTime.setMinutes(dateTime.getMinutes() + dateTime.getTimezoneOffset() + 60);
  127. }
  128.  
  129. const midnight = new Date(dateTime.getTime());
  130. midnight.setHours(0, 0, 0, 0);
  131.  
  132. const msPastMidnight = dateTime - midnight;
  133. const decimal = (msPastMidnight / 86400000) * 1000;
  134. return Math.floor((decimal + Number.EPSILON) * 100) / 100
  135. }
  136. })();