eBay Local Time

Converts times on ebay.com and ebay.co.uk to the user's local time

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
//
// @name          eBay Local Time
// @description   Converts times on ebay.com and ebay.co.uk to the user's local time
// @author        nice_bow_tie
//
// @homepage      http://userscripts.org/scripts/show/153187
//
// @include       http://*.ebay.*
//
// @grant         GM_getValue
// @grant         GM_setValue
//
// @version       1.2
//
// @history       1.2  Add option to toggle between 24 hour and AM/PM display
// @history       1.1  Convert some additional date-time fields
// @history       1.0  Initial release
//
// @namespace https://greasyfork.org/users/2542
// ==/UserScript==


(function(){

	//RFC-2822 month names
	var standard_months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
	                       "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

	var eng_long_months = ["January", "February", "March", "April", "May",
	                       "June", "July", "August", "September", "October",
	                       "November", "December"];

	var eng_weekdays    = ["Sunday", "Monday", "Tuesday", "Wednesday",
	                       "Thursday", "Friday", "Saturday"];

	var site_data = {};

	//=========================================================================
	// site_data contains the data used by the script for each supported site
	//=========================================================================
	//
	// site_data[site].abbr_month_names
	// ------------------------------------------------------------------------
	//    an array of short month names used on the site
	//
	// site_data[site].long_month_names
	// ------------------------------------------------------------------------
	//    an array of long month names used on the site
	//
	// site_data[site].weekday_names
	// ------------------------------------------------------------------------
	//    an array of weekday names used on the site
	//
	// site_data[site].timezones
	// ------------------------------------------------------------------------
	//    an array of timezones used on the site, such as PST or BST
	//    the timezone name is followed by the signed 4-digit offset from UTC,
	//    for example "PST:-0800"
	//
	// site_data[site].timezone_periods
	// ------------------------------------------------------------------------
	//    an object defining valid time periods for each timezone, used to find
	//    the correct timezone when it's not part of the displayed time;
	//    each entry is an array of time periods in the format
	//    YYYYMMDDHHMMSS-YYYYMMDDHHMMSS, which define the times when that
	//    timezone is used
	//
	// site_data[site].locations
	// ------------------------------------------------------------------------
	//    an array of {selector, format} objects that identify the location and
	//    format of the times the script should change:
	//    ---------------------------------------------------------------------
	//    'selector' = a CSS selector string that selects the nodes containing
	//    times in the format described by 'format'
	//    if the time is split across > 1 node (eg. the date in one node and
	//    the time in another), those nodes must be consecutive in the list of
	//    nodes matching the selector
	//    ---------------------------------------------------------------------
	//    'format' = a string describing the format of the date/time text in
	//    the nodes (eg. "m-d-y|h:i:s T")
	//    the following placeholders are used to indicate where in the text
	//    that component should appear:
	//            W     : weekday name
	//            d     : 2-digit day number (01-31)
	//            D     : 1 or 2-digit day number (1-31)
	//            m     : short month name
	//            M     : long month name
	//            n     : 2-digit month number (01-12)
	//            Y     : 4-digit year
	//            y     : 2-digit year
	//            h     : 2-digit hour (00-23)
	//            i     : 2-digit minute (00-59)
	//            s     : 2-digit second (00-59)
	//            T     : timezone
	//    other special characters are:
	//            space : matches one or more whitespace characters
	//            |     : indicates where the text is split across nodes
	//    all other characters in the format string are treated as literals
	//
	//=========================================================================

	site_data["ebay.com"] = {};

	site_data["ebay.com"].abbr_month_names = standard_months;
	site_data["ebay.com"].long_month_names = eng_long_months;
	site_data["ebay.com"].weekday_names    = eng_weekdays;
	site_data["ebay.com"].timezones        = ["PST:-0800", "PDT:-0700"];

	site_data["ebay.com"].timezone_periods =
	{
		PST: [ "20080101000000-20080309015959",
		       "20081102020000-20090308015959",
		       "20091101020000-20100314015959",
		       "20101107020000-20110313015959",
		       "20111106020000-20120311015959",
		       "20121104020000-20130310015959",
		       "20131103020000-20140309015959",
		       "20141102020000-20150308015959",
		       "20151101020000-20160313015959",
		       "20161106020000-20170312015959",
		       "20171105020000-20180311015959",
		       "20181104020000-20190310015959",
		       "20191103020000-20200308015959"
		     ],

		PDT: [ "20080309030000-20081102005959",
		       "20090308030000-20091101005959",
		       "20100314030000-20101107005959",
		       "20110313030000-20111106005959",
		       "20120311030000-20121104005959",
		       "20130310030000-20131103005959",
		       "20140309030000-20141102005959",
		       "20150308030000-20151101005959",
		       "20160313030000-20161106005959",
		       "20170312030000-20171105005959",
		       "20180311030000-20181104005959",
		       "20190310030000-20191103005959"
		     ]
	};

	site_data["ebay.com"].locations        =
	[
		//view item page
		{ selector : "div.lable~div span, .vi-is1-dt-eu>span, .vi-is1-dt>span",
		  format   : "m d, Y|h:i:s T"                },

		//revision summary page / bid history page
		{ selector : ".pagecontainer td:nth-child(-n+2), td>div>span, .titleValueFont",
		  format   : "m-d-y|h:i:s T"                 },

		//purchase history page / bid history page (ending time)
		{ selector : ".pagecontainer td, .titleValueFont",
		  format   : "m-d-y h:i:s T"                 },

		//'last revised' time
		{ selector : ".vi-desc-revHistory div, .vi-cmb>div",
		  format   : "m d, Y h:i:s T"                },

		//item page new design: active listings
		{ selector : ".vi-VR-enddate-sec, .rev-date",
		  format   : "m d, Y h:i:s T"                },

		//item page new design: active listings
		{ selector : ".vi-VR-enddate-no-sec",
		  format   : "m d h:i:s T"                   },

		//item new page design: completed listings
		{ selector : ".vi-ended, .vi-ended .endedDate span",
		  format   : "m d, Y|h:i:s T"                },

		//my ebay
		{ selector : ".my_itl-itR div>span",
		  format   : "n/d/y at h:i:s"                },

		//search results
		{ selector : "span.tme>span, div.timeLeftInfo>span",
		  format   : "m-d h:i"                       },

		//feedback
		{ selector : ".FbOuterYukon td:last-child",
		  format   : "m-d-y h:i"                     },
	];

	site_data["ebay.co.uk"] = {};

	site_data["ebay.co.uk"].abbr_month_names = standard_months;
	site_data["ebay.co.uk"].long_month_names = eng_long_months;
	site_data["ebay.co.uk"].weekday_names    = eng_weekdays;
	site_data["ebay.co.uk"].timezones        = ["GMT:+0000", "BST:+0100"];

	site_data["ebay.co.uk"].timezone_periods =
	{
		GMT: [ "20080101000000-20080330005959",
		       "20081026020000-20090329005959",
		       "20091025020000-20100328005959",
		       "20101031020000-20110327005959",
		       "20111030020000-20120325005959",
		       "20121028020000-20130331005959",
		       "20131027020000-20140330005959",
		       "20141026020000-20150329005959",
		       "20151025020000-20160327005959",
		       "20161030020000-20170326005959",
		       "20171029020000-20180325005959",
		       "20181028020000-20190331005959",
		       "20191027020000-20200329005959"
		     ],

		BST: [ "20080330020000-20081026005959",
		       "20090329020000-20091025005959",
		       "20100328020000-20101031005959",
		       "20110327020000-20111030005959",
		       "20120325020000-20121028005959",
		       "20130331020000-20131027005959",
		       "20140330020000-20141026005959",
		       "20150329020000-20151025005959",
		       "20160327020000-20161030005959",
		       "20170326020000-20171029005959",
		       "20180325020000-20181028005959",
		       "20190331020000-20191027005959"
		     ]
	};

	site_data["ebay.co.uk"].locations        =
	[
		//view item page
		{ selector : "div.lable~div span, .vi-is1-dt-eu>span, .vi-is1-dt>span",
		  format   : "d m, Y|h:i:s T"                },

		//revision summary page / bid history page
		{ selector : ".pagecontainer td:nth-child(-n+2), td>div>span, .titleValueFont",
		  format   : "d-m-y|h:i:s T"                 },

		//purchase history page / bid history page (ending time)
		{ selector : ".pagecontainer td, .titleValueFont",
		  format   : "d-m-y h:i:s T"                 },

		//'last revised' time
		{ selector : ".vi-desc-revHistory div, .vi-cmb>div",
		  format   : "d m, Y h:i:s T"                },

		//item page new design: active listings
		{ selector : ".vi-VR-enddate-sec, .rev-date",
		  format   : "d m, Y h:i:s T"                },

		//item page new design: active listings
		{ selector : ".vi-VR-enddate-no-sec",
		  format   : "d m h:i:s T"                   },

		//item new page design: completed listings
		{ selector : ".vi-ended, .vi-ended .endedDate span",
		  format   : "d m, Y|h:i:s T"                },

		//my ebay
		{ selector : ".my_itl-itR div>span",
		  format   : "d/n/y at h:i:s"                },

		//search results
		{ selector : "span.tme>span, div.timeLeftInfo>span",
		  format   : "d-m h:i"                       },

		//feedback
		{ selector : ".FbOuterYukon td:last-child",
		  format   : "d-m-y h:i"                     },
	];

	//=========================================================================

	//get the site data for the current page
	var hostname = window.location.hostname;
	while (hostname && !site_data[hostname])
		hostname = hostname.substr(hostname.indexOf(".")+1 || 9999);
	if (!(site_data = site_data[hostname]))
		return;

	var timezones = [];
	site_data.timezones.forEach(function(tz) {
		timezones = timezones.concat(tz.split(":"));});

	var tz_periods = [];
	timezones.forEach(function(tz)
	{
		if (site_data.timezone_periods && site_data.timezone_periods[tz])
		{
			site_data.timezone_periods[tz].forEach(function(tz_period)
			{
				if (/^\d{14}-\d{14}$/.test(tz_period))
				{
					var a = tz_period.split("-");
					tz_periods.push({start:a[0], end:a[1], tz:tz});
				}
			});
		}
	});

	//regexps that match the format string placeholders
	var regexp_strings =
	{
		W: site_data.weekday_names.join("|"),
		d: "0?[1-9]|[1-2][0-9]|3[0-1]",
		D: "[1-9]|[1-2][0-9]|3[0-1]",
		m: site_data.abbr_month_names.join("|"),
		M: site_data.long_month_names.join("|"),
		n: "0[1-9]|1[012]",
		Y: "20[0-2][0-9]",
		y: "[0-2][0-9]",
		h: "[0-1][0-9]|2[0-3]",
		i: "[0-5][0-9]",
		s: "[0-5][0-9]",
		T: timezones.join("|").replace(/[+-]/g, "\\$&"),
	};

	var use_12_hour_clock = false;
	if (typeof GM_getValue == "function")
		use_12_hour_clock = !!GM_getValue("Use12HourClock");
	
	//chrome workaround
	if (window.chrome)
		use_12_hour_clock = JSON.parse(localStorage.getItem("Use12HourClock"));

	(function ProcessPage()
	{
		var found_nodes = [];

		//find and process the nodes matching each location selector
		site_data.locations.forEach(function(location_data)
		{
			var nodes = [];
			var qsa = document.querySelectorAll(location_data.selector.replace(/\s*(,|$)/g, ":not([nbt-processed])$1"));
			for (var i = 0; i < qsa.length; i++)
			{
				found_nodes.push(qsa[i]);
				for (var j = 0; j < qsa[i].childNodes.length; j++)
				{
					var child = qsa[i].childNodes[j];
					if(child.nodeType == 3 && child.data.match(/\S/))
						nodes.push(child);
				}
			}
			var format_array = location_data.format.split("|");
			var node_count = format_array.length;
			while (nodes.length >= node_count)
			{
				if (!ProcessNode(nodes, format_array, node_count))
					nodes.shift();
			}
		});

		found_nodes.forEach(function(n){n.setAttribute("nbt-processed", 1);});
		window.setTimeout(ProcessPage, 2000);
	})();

	//process node_count text nodes from the node_list
	//if successful, remove the matched node(s) and return true
	function ProcessNode(node_list, format_array, node_count)
	{
		var node_data = [];
		for (var i = 0; i < node_count; i++)
		{
			var format_string = format_array[i];

			//create regexp from the format string and match against the text
			var re = "";
			for (var j = 0; j < format_string.length; j++)
			{
				var c = format_string[j];
				var str = regexp_strings[c] ||
				          c.replace(/[-.+\\{}?*()|[\]]/, "\\$&")
				           .replace(" ", "\\s+");
				re += "(" + str + ")";
			}
			re = "^([\\s\\S]*?)" + re + "([\\s\\S]*?)$";

			var match_result;
			if (match_result = node_list[i].data.match(re))
			{
				match_result.shift();
				match_result.prefix = match_result.shift();
				match_result.suffix = match_result.pop();
				//match_result[0] -> match_result[n] now contain the node text
				//corresponding to characters 0 -> n of the format string,
				//also set node_data[c] to the text corresponding to each
				//placeholder c in the format string
				for (var j = 0; j < format_string.length; j++)
				{
					var c = format_string[j];
					if (regexp_strings[c])
						node_data[c] = match_result[j];
				}
				node_data.push(match_result);
			}
			else return;
		}

		//check it's valid
		if (node_data.length != node_count
			|| !(node_data['m'] || node_data['M'] || node_data['n'])
			|| !(node_data['d'] || node_data['D'])
			|| !(node_data['h'])
		)
			return;

		//create array of strings to be used in the Date.parse argument
		var parse_array = [];
		parse_array[0] = node_data['d'] || node_data['D'];
		var month_number =
			site_data.abbr_month_names.indexOf(node_data['m'])+1 ||
			site_data.long_month_names.indexOf(node_data['M'])+1 ||
			node_data['n'];
		parse_array[1] = standard_months[month_number-1];
		var year = node_data['Y'] || ("20" + node_data['y']);
		if (year.length != 4) //if not found, assume it's the current year
			year = new Date().getFullYear() + "";
		parse_array[2] = year;
		parse_array[3] = node_data['h'];
		parse_array[4] = node_data['i'] || "00";
		parse_array[5] = node_data['s'] || "00";

		if (!node_data['T'])
		{
			//no timezone specified, so get it from tz_periods
			var date_str = year;
			date_str += ("0"+month_number).substr(-2);
			date_str += ("0"+parse_array[0]).substr(-2);
			date_str += parse_array[3] + parse_array[4] + parse_array[5];
			tz_periods.some(function(tz_period)
			{
				if (date_str >= tz_period.start && date_str <= tz_period.end)
					return (node_data['T'] = tz_period.tz);
			});

			if (!node_data['T'])
				return;
		}

		var next_tz = timezones[timezones.indexOf(node_data['T'])+1];
		if (!/^[+-]\d{4}$/.test(next_tz))
			return;
		parse_array[6] = next_tz;

		//parse the time using RFC-2822 format
		var ts = Date.parse("0 1 2 3:4:5 6".replace(/\d/g,
						function(i) {return parse_array[i];}));

		if (!isNaN(ts))
		{
			function fmt(n, pad) {return (n<10 && pad) ? "0"+n : ""+n;}

			//convert to local time
			var date = new Date(ts);
			var new_date =
			{
				W: site_data.weekday_names[date.getDay()],
				d: fmt(date.getDate(), true),
				D: fmt(date.getDate(), false),
				m: site_data.abbr_month_names[date.getMonth()],
				M: site_data.long_month_names[date.getMonth()],
				n: fmt(date.getMonth()+1, true),
				Y: fmt(date.getFullYear()),
				y: fmt(date.getFullYear()).substr(2),
				h: fmt(date.getHours(), true),
				i: fmt(date.getMinutes(), true),
				s: fmt(date.getSeconds(), true),
			};

			//save the original date/time for the tooltip
			var tooltip = [];
			for (var i = 0; i < node_count; i++)
				tooltip.push(node_data[i].join(""));
			tooltip = tooltip.join(" ").replace(/\s+/g, " ");

			//update the node data with the new local time values
			var changed = false;
			node_data.forEach(function(node_data, i)
			{
				var am_pm = "";
				var format_str = format_array[i];
				for (var j = 0; j < format_str.length; j++)
				{
					var c = format_str[j];
					if (c == "T" || (c == " " && format_str[j+1] == "T"))
						node_data[j] = ""; //remove the timezone
					else if (new_date[c] && new_date[c] != node_data[j])
					{
						node_data[j] = new_date[c];
						changed = true;
						if (c == "h" && use_12_hour_clock)
						{
							//change to 12 hour time
							var hours = +node_data[j];
							am_pm = (hours < 12) ? " AM" : " PM";
							node_data[j] = (hours + 11) % 12 + 1;
						}
					}
				}
				if (am_pm != "")
					node_data.push(am_pm);
			});

			for (var i = 0; i < node_count; i++)
			{
				var node = node_list.shift();

				if (changed)
				{
					//the time has changed, so update the DOM with the new time
					var new_node = document.createElement("span");
					new_node.className = "nbt-localtime";
					new_node.style.fontSize = "inherit";
					var prefix = node_data[i].prefix;
					if (prefix)
						new_node.appendChild(document.createTextNode(prefix));
					var span = document.createElement("span");
					span.title = tooltip;
					span.style.fontSize = "inherit";
					var t = document.createTextNode(node_data[i].join(""));
					span.appendChild(t);
					new_node.appendChild(span);
					var suffix = node_data[i].suffix;
					if (suffix)
						new_node.appendChild(document.createTextNode(suffix));

					if (i == node_count-1)
					{
						var star = document.createElement("b");
						star.style.color = "#00a000";
						star.style.fontSize = "inherit";
						star.style.fontWeight = "normal";
						star.style.cursor = "default";
						star.appendChild(document.createTextNode(" * "));

						var ref_node = new_node.lastChild;
						var index = suffix.search(/\s/);
						if (index == 0)
							ref_node = span;
						else if (index > 0)
							new_node.lastChild.splitText(index);

						new_node.insertBefore(star, ref_node.nextSibling);

						if (window.chrome)
						{
							//workaround for a Chrome (Webkit) bug that causes
							//the star to wrap onto a new line
							if (node.parentNode.className == "endedDate")
							{
								var bugfixer = document.createTextNode(" ");
								node.parentNode.insertBefore(bugfixer, node);
							}
						}
					}

					new_node.addEventListener("dblclick", ToggleTimeDisplay, false);
  					node.parentNode.replaceChild(new_node, node);
				}
			}

			return true;
		}
	}

	function ToggleTimeDisplay()
	{
		var success = false;
		if (typeof GM_setValue == "function")
		{
			GM_setValue("Use12HourClock", !use_12_hour_clock);
			success = (GM_getValue("Use12HourClock") === !use_12_hour_clock);
		}
		
		//chrome workaround
		if (window.chrome)
		{
			localStorage.setItem("Use12HourClock", !use_12_hour_clock);
			success = (JSON.parse(localStorage.getItem("Use12HourClock")) === !use_12_hour_clock);
		}

		if (success)
			alert("eBay Local Time display has been changed to: " +
				(use_12_hour_clock ? "24 hour clock" : "12 hour clock (AM/PM)") +
				"\n\n(Takes effect from next page load)");
		else
			alert("Error: eBay Local Time display setting could not be changed " +
				"(requires Greasemonkey or a Greasemonkey-compatible userscript manager)");
	}

})()