Gmail date formater

display full dates on gmail

// ==UserScript==
// @name         Gmail date formater
// @namespace    https://monkeyr.com/
// @version      0.1
// @description  display full dates on gmail
// @author       mhume
// @match        https://mail.google.com/mail/u/0/*
// @grant        none
// ==/UserScript==

(function () {
	'use strict';

	var GmailDates = {

		init : function () {
			// add the custom methods to our custom observer
			this.customObserver.prototype = {
				connect : function () {
					this.ob.observe(this.target, this.config);
				},
				disconnect : function () {
					this.ob.disconnect();
				}
			};
			// make reference within GmailDates to the custom observer so it can be paused when we mutate the dom
			this.ob = new this.customObserver(document, false, this.main_mutation_callback.bind(this));
			this.ob.connect();
		},

		main_mutation_callback : function (mutations) {
			var _this = this;
			mutations.forEach(function (mutation) {
				var added = mutation.addedNodes;
				for ( var node, i = added.length; (node = added[--i]); ) {
					var j, tab, colgrp, span, tds, td, cols, col,
						txtNode = (node.nodeName === '#text');
					//console.log(node.nodeName, txtNode, node);
					if(txtNode){
						node = mutation.target;
					}
					switch(node.nodeName){
						case 'DIV':
							// finds the div/table that contains the initial list of emails
							if ( node.hasAttribute('class') === false && (tab = _this.get_first_child(node)) && tab.nodeName === 'TABLE' ) {
								var width;
								tds = node.querySelectorAll('.xW.xY,.apm');// .apm for dates in classic vertical split
								for ( j = tds.length; (td = tds[--j]); ) {
									if( !width ){
										width = td.classList.contains('apm') ? '16ex' : '14ex'; //classic vertical split needs a slightly different width
									}
									_this.handle_dates_in_list(td); // change the date formats on the email list
								}
								if ( (colgrp = _this.get_first_child(tab)) && colgrp.nodeName === 'COLGROUP' ) {
									cols = colgrp.querySelectorAll('.xX');
									for ( j = cols.length; (col = cols[--j]); ) {
										col.style.width = width; // sets date width on classic gmail
									}
								}

							} else
							// the datetime on individual email views
							if ( node.getAttribute('role') === 'listitem' && (span = _this.get_first_child_by_class(node, '.g3')) ) {
								_this.handle_date_on_email(span);
							}
							break;

						case 'TD':
							// handles individual email date times in split view
							if ( node.getAttribute('class') === 'Bu' && (span = _this.get_first_child_by_class(node, '.g3')) ) {
								_this.handle_date_on_email(span);
							} else
							// handles individual row updates in a list. This happens when emails slip from today to yesterday without a page refresh
							if ( node.classList.contains('xW') && node.classList.contains('xY') ) {
								_this.handle_dates_in_list(node);
							}
							break;

						case 'SPAN':
							// the datetime on individual email views as the minutes then hours grow
							if ( node.classList.contains('g3') ) {
								_this.handle_date_on_email(node);
							} else
							// the datetime on New gmail, split screen lists after selection
							if ( node.attributes.length == 0 ) {
								_this.child_handle_dates_in_list(node);
							} else
							// the datetime on Classic gmail, horizontal split screen lists after selection
							if ( node.hasAttribute('aria-label') ) {
								_this.child_handle_dates_in_list(node);
							} else
							// the datetime on New gmail when an unread idem is selected in horizontal split
							if ( node.classList.contains('bq3') ) {
								_this.child_handle_dates_in_list(node);
							}
							//console.log('#text update', txtNode, node);
							break;

						case 'B':
							// the datetime on Classic gmail, horizontal split screen lists after selection
							if ( node.attributes.length == 0 ) {
								_this.child_handle_dates_in_list(node);
							}
							break;
					}
				}
			});
		},

		get_first_child_by_class : function(cont, cls){
			var eles = cont.querySelectorAll(cls);
			return eles.length ? eles[0] : false;
		},

		child_handle_dates_in_list : function(child){
			var node;
			if ( (node = child.closest('tr')) && (node = this.get_first_child_by_class(node, '.xW.xY')) ) {
				this.handle_dates_in_list(node);
			}
		},

		handle_dates_in_list : function (cont) {
			cont.style.maxWidth = '100px';
			//console.log(cont);
			var span1 = this.get_first_child(cont),
				span2 = this.get_first_child(span1),
				datetime = span1.getAttribute('aria-label').replace(/ at /, ' '), // classic gmail has the following format, 11 July 2018 at 11:56
				dat = new Date(datetime);
			this.ob.disconnect(); //disable before we mutate the dom
			// span2 doesn't exist in classic gmail
			(span2 || span1).innerHTML = this.format_date(dat);
			this.ob.connect(); //enable again
		},

		handle_date_on_email : function (span) {
			//console.log(span);
			var match = span.innerHTML.match(/.*( \([\da-zA-Z ]+\))/),
				datetime = span.getAttribute('alt').replace(/ at /, ' '), // classic gmail has the following format, 11 July 2018 at 11:56
				dat = new Date(datetime)
				;
			//console.log('match', span.innerHTML, match);
			if ( match ) {
				this.ob.disconnect(); //disable before we mutate the dom
				span.innerHTML = this.format_date(dat) + match[1];
				this.ob.connect(); //enable again
			}
		},

		format_date : function (dat) {
			return dat.toISOString().substring(0, 10) + ' ' + dat.getHours().pad() + ':' + dat.getMinutes().pad();
		},

		get_first_child : function (el) {
			var first = el.firstChild;
			while ( first != null && first.nodeType == 3 ) { // skip TextNodes
				first = first.nextSibling;
			}
			return first;
		},

		customObserver : function (target, config, callback) {
			this.target = target || document;
			this.config = config || {childList : true, subtree : true};
			var _this = this;
			this.ob = new MutationObserver(function (mutations) {
				callback.call(_this, mutations);
			});
		}

	};

	// allow padding of numbers
	Number.prototype.pad = function (size) {
		var s = String(this);
		while ( s.length < (size || 2) ) {
			s = "0" + s;
		}
		return s;
	};


	GmailDates.init();


})();