User:Bamyers99/UnitsConverter.js

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
/**
 Copyright 2017 Myers Enterprises II

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.

 Description
 ===========
 Convert a unit to another unit.
 Supports currency, metric, United States units

 Usage
 =====
 Add the following line(s) to your [[Special:Mypage/common.js]] page

 Bamyers99_UnitsConverter_currency = 'EUR'; // AUD,BRL,CAD,CHF,CNY,DKK,EUR,GBP,INR,ISK,JPY,MXN,NOK,NZD,PLN,RUB,SEK,TRY,USD,ZAR
 importScript("User:Bamyers99/UnitsConverter.js");

 */

var Bamyers99 = Bamyers99 || {};

if (typeof Bamyers99_UnitsConverter_testmode === 'undefined') Bamyers99_UnitsConverter_testmode = false;
if (typeof Bamyers99_UnitsConverter_currency === 'undefined') Bamyers99_UnitsConverter_currency = 'EUR';

Bamyers99.UnitsConverter = {
	commonjs: Bamyers99_UnitsConverter_testmode ? 'https://bambots.brucemyers.com/GadgetCommon.js' :
		'https://www.wikidata.org/w/index.php?title=User:Bamyers99/GadgetCommon.js&action=raw&ctype=text/javascript',
	unitCurrencies: ['Q1104069','Q122922','Q123213','Q131473','Q132643','Q1472704','Q172872','Q173117','Q181907',
	                 'Q25224','Q25344','Q25417','Q259502','Q39099','Q41044','Q4730','Q4916','Q4917','Q80524','Q8146'],
	unitConverts: {
		// mass
		'Q41803': [{'mult':0.035274, 'unit': 'Q48013'}], // gram -> ounce
		'Q48013': [{'mult':28.34952, 'unit': 'Q41803'}], // ounce -> gram
		'Q11570': [{'mult':2.204622, 'unit': 'Q100995'}], // kilogram -> pound
		'Q100995': [{'mult':0.453592, 'unit': 'Q11570'}], // pound -> kilogram
		// dimensions
		'Q174789': [{'mult':0.03937, 'unit': 'Q218593'}], // millimeter -> inch
		'Q174728': [{'mult':0.3937, 'unit': 'Q218593'}], // centimetre -> inch
		'Q218593': [{'mult':2.54, 'unit': 'Q174728'}], // inch -> centimetre
		'Q11573': [{'mult':3.28084, 'unit': 'Q3710'}], // metre -> foot
		'Q3710': [{'mult':0.3048, 'unit': 'Q11573'}], // foot -> metre
		'Q482798': [{'mult':0.9144, 'unit': 'Q11573'}], // yard -> metre
		'Q828224': [{'mult':0.621371, 'unit': 'Q253276'}], // kilometre -> mile
		'Q253276': [{'mult':1.609344, 'unit': 'Q828224'}], // mile -> kilometre
		// area
		'Q2489298': [{'mult':0.155, 'unit': 'Q1063786'}], // sq centimetre -> sq inch
		'Q1063786': [{'mult':6.4516, 'unit': 'Q2489298'}], // sq inch -> sq centimetre
		'Q25343': [{'mult':10.7639, 'unit': 'Q857027'}], // sq metre -> sq foot
		'Q857027': [{'mult':0.092903, 'unit': 'Q25343'}], // sq foot -> sq metre
		'Q1550511': [{'mult':0.836127, 'unit': 'Q11573'}], // sq yard -> sq metre
		'Q35852': [{'mult':2.471054, 'unit': 'Q81292'}], // hectare -> acre
		'Q81292': [{'mult':0.404686, 'unit': 'Q35852'}], // acre -> hectare
		'Q712226': [{'mult':0.386102, 'unit': 'Q232291'}], // sq kilometre -> sq mile
		'Q232291': [{'mult':2.59, 'unit': 'Q712226'}], // sq mile -> sq kilometre
		// temperature
		'Q25267': [{'mult':1.8, 'addafter':32, 'unit': 'Q42289'}], // C -> F
		'Q42289': [{'subbefore': 32, 'mult':0.556, 'unit': 'Q25267'}], // F -> C
		'Q11579': [{'mult':1, 'subafter': 273.15, 'unit': 'Q25267'}, // K -> C
		           {'mult':1.8, 'subafter': 459.67, 'unit': 'Q42289'}], // K -> F
		// speed
		'Q182429': [{'mult':3.28084, 'unit': 'Q748716'}], // meter / second -> foot / second
		'Q748716': [{'mult':0.3048, 'unit': 'Q182429'}], // foot / second -> meter / second
		'Q180154': [{'mult':0.621371, 'unit': 'Q211256'}], // kilometer / hour -> mile / hour
		'Q211256': [{'mult':1.609344, 'unit': 'Q180154'}], // mile / hour -> kilometer / hour
		'Q128822': [{'mult':1.15078, 'unit': 'Q211256'}, // knot -> mile / hour
		           {'mult':1.852, 'unit': 'Q180154'}], // knot -> kilometer / hour
	},
	propPointInTime: 'P585',
	defaultCurISO: 'EUR',

	/**
	 * Init
	 */
	init: function() {
		var self = this ;

		$.when(
			$.ajax( { url: self.commonjs, dataType: 'script', cache: true } ),
			mw.loader.using( 'mediawiki.language' )
		).done( function() {
			self.gc = Bamyers99.GadgetCommon;

			mw.hook( 'wikibase.entityPage.entityLoaded' ).add( function ( data ) {
				'use strict';

				var pointInTimeYear = null;
				var propHits = [];

				$.each( data.claims || {}, function ( prop, claims ) {

					$.each( claims, function ( index, claim ) {
						var qpointInTimeYear = null;
						var qualOffset = 0;
						var qualifierHits = [];

						if ( ! claim.mainsnak || ! claim.mainsnak.datavalue ) return true;

						var dv = claim.mainsnak.datavalue;

						if ( prop === self.propPointInTime ) {
							pointInTimeYear = self.parseDateToYear( dv );
							if ( pointInTimeYear ) return false;
							return true;
						}

						var claimQualifiers = {};

						if ( claim['qualifiers-order'] && claim.qualifiers ) {
							$.each( claim['qualifiers-order'], function ( i, prop ){
								claimQualifiers[prop] = claim.qualifiers[prop];
							});
						} else if ( claim.qualifiers ) {
							claimQualifiers = claim.qualifiers;
						}

						$.each( claimQualifiers, function ( prop, qualifiers) {

							$.each( qualifiers, function ( index, qualifier ) {
								if ( ! qualifier.datavalue ) {
									++qualOffset;
									return true;
								}

								if ( prop === self.propPointInTime ) {
									qpointInTimeYear = self.parseDateToYear( qualifier.datavalue );
									++qualOffset;
									if ( qpointInTimeYear ) return false;
									return true;
								}

								if ( qualifier.datavalue.type !== 'quantity' ) {
									++qualOffset;
									return true;
								}

								var quantity = self.parseQuantity( qualifier.datavalue );
								if ( quantity === null || ( ! self.unitConverts.hasOwnProperty( quantity.unit ) &&
										self.unitCurrencies.indexOf( quantity.unit ) === -1 )) {
									++qualOffset;
									return true;
								}

								qualifierHits.push( {'cid': claim.id, 'qoffset': qualOffset,
									'unit': quantity.unit, 'amount': quantity.amount} );
								++qualOffset;
							});
						});

						for ( var i = 0, al = qualifierHits.length; i < al; ++i ) {
							if ( qpointInTimeYear ) qualifierHits[i].pity = qpointInTimeYear;
							propHits.push( qualifierHits[i] );
						}

						if ( dv.type !== 'quantity' ) {
							return true;
						}

						var quantity = self.parseQuantity( dv );
						if ( quantity === null || ( ! self.unitConverts.hasOwnProperty( quantity.unit ) &&
								self.unitCurrencies.indexOf( quantity.unit ) === -1 )) {
							return true;
						}

						propHits.push( {'cid': claim.id, 'unit': quantity.unit,
							'amount': quantity.amount, 'pity': qpointInTimeYear} );
					});
				});

				var currencyFound = false;

				for ( var i = 0, al = propHits.length; i < al; ++i ) {
					if (pointInTimeYear && ! propHits[i].hasOwnProperty( 'pity' ) ) propHits[i].pity = pointInTimeYear;
					if ( ! currencyFound && self.unitCurrencies.indexOf( propHits[i].unit ) !== -1 ) currencyFound = true;
				}

				if ( currencyFound ) {
					$.getJSON( '/w/index.php?title=User:Bamyers99/currency.json&action=raw&ctype=application/json', function( data ) {
						self.displayUnits( propHits, data );
					});
				} else {
					self.displayUnits( propHits, null );
				}
			});
		});
	},

	/**
	 * Display converted units
	 *
	 * @param propHits
	 * @param currencies
	 */
	displayUnits: function ( propHits, currencies ) {
		var self = this;
		var toUnits = {};
		var toCurUnit = null;

		// Get the 'to unit' language labels
		if ( currencies ) {
			if ( ! currencies.ISOs.hasOwnProperty( Bamyers99_UnitsConverter_currency ) )
				Bamyers99_UnitsConverter_currency = self.defaultCurISO;
			toCurUnit = currencies.ISOs[Bamyers99_UnitsConverter_currency];
			toUnits[toCurUnit] = '';
		}

		for ( var i = 0, al = propHits.length; i < al; ++i ) {
			if ( self.unitConverts.hasOwnProperty( propHits[i].unit ) ) {
				for ( var x = 0, cl = self.unitConverts[propHits[i].unit].length; x < cl; ++x )
					toUnits[self.unitConverts[propHits[i].unit][x].unit] = '';
			}
		}

		var opts = {
			action: 'wbgetentities',
			props: 'labels',
			ids: $.map( toUnits, function( v, k ) {
					return k;
				} ).join( '|' ),
			lang: 'wikidata'
		};

		self.gc.mwApiQuery( opts, function( data ) {
			if ( data.entities ) {
				var userLang = mw.config.get( 'wgUserLanguage' ) ;

				$.each( data.entities, function( id, itemdata ) {
					if ( itemdata.labels ) {
						var label = ( itemdata.labels[userLang] && itemdata.labels[userLang].value ) ||
							( itemdata.labels.en && itemdata.labels.en.value ) || false;
						if ( label ) toUnits[id] = label;
					}
				});
			}

			$.each( propHits, function( i, hit ) {
				var $claimview = $( '.wikibase-statementview' )
					.filter( function () {
						return $( this ).hasClass( 'wikibase-statement-' + hit.cid );
					});

				if ( hit.hasOwnProperty( 'qoffset' ) ) {
					$claimview = $claimview.find( '.wikibase-statementview-qualifiers .wikibase-snakview' ).eq( hit.qoffset );
				} else {
					$claimview = $claimview.find( '.wikibase-statementview-mainsnak' );
				}

				var h = '';
				var toLabel, val;

				if ( self.unitConverts.hasOwnProperty( hit.unit ) ) {
					$.each( self.unitConverts[hit.unit], function ( i, convert) {
						toLabel = toUnits[convert.unit];
						val = hit.amount;
						if ( convert.subbefore ) val -= convert.subbefore;
						val *= convert.mult;
						if ( convert.addafter ) val += convert.addafter;
						if ( convert.subafter ) val -= convert.subafter;

						if ( val < 5 ) val = val.toFixed(1);
						else val = Math.round(val);

						val = mw.language.convertNumber( val );

						h += '<br />(' + val + ' <span class="wb-unit">' + self.gc.htmlEncode( toLabel ) + '</span>)';
					})
				} else {
					var fromCurUnit = hit.unit;
					toLabel = toUnits[toCurUnit];
					var mults = [0,0];
					var fromYear, toYear;
					var currentYear = '' + currencies.current_year;

					if ( hit.pity ) {
						fromYear = hit.pity;
						if ( currencies[fromCurUnit].cpis[fromYear] ) toYear = currentYear;
						else toYear = fromYear;
					} else {
						fromYear = toYear = currentYear;
					}

					if ( fromCurUnit === toCurUnit && fromYear === toYear ) return true;

					if ( ! currencies[fromCurUnit].rates[fromYear] ||
						! currencies[toCurUnit].rates[toYear] ) return true;

					$.each( [fromYear, toYear], function( x, year ) {
						var multToCheck = ( x === 0 ) ? currencies[fromCurUnit].multipliers : currencies[toCurUnit].multipliers;
						year = parseInt(year, 10);

						$.each( multToCheck , function( y, multRange ) {
							if ( year >= multRange.start && year <= multRange.end ) {
								mults[x] = multRange.mult;
								return false;
							}
						});
					});

					if (mults[0] === 0 || mults[1] === 0 ) return true;

					var startCPI = currencies[fromCurUnit].cpis[fromYear];
					var endCPI = currencies[fromCurUnit].cpis[toYear];
					var fromRate = currencies[fromCurUnit].rates[fromYear];
					var toRate = currencies[toCurUnit].rates[toYear];
					
					// Calculate from year converted amount
					if (fromCurUnit != toCurUnit && currencies[toCurUnit].rates[fromYear]) {
						val = hit.amount / fromRate * currencies[toCurUnit].rates[fromYear];
						val = Math.round(val);
						val = mw.language.commafy( val, '#,##0' );
						
						h += '<br />(' + val + ' <span class="wb-unit">' + self.gc.htmlEncode( toLabel ) + '</span>';
						if ( hit.pity ) h += ' approximate (' + fromYear + ')';
						h += ')';
					}

					// Inflate 'from currency' amount
					if (fromCurUnit == toCurUnit) {
					    val = hit.amount / mults[0] * endCPI / startCPI * mults[1];
					} else {
					    val = hit.amount / mults[0] / fromRate * endCPI / startCPI * toRate * mults[1];
					}
					
					if (! isNaN(val)) {
					    val = Math.round(val);
					    val = mw.language.commafy( val, '#,##0' );
					
					    h += '<br />(' + val + ' <span class="wb-unit">' + self.gc.htmlEncode( toLabel ) + '</span>';
					    if ( hit.pity ) h += ' approximate (' + toYear + ')';
					    h += ')';
					}
				}

				$claimview.find( '.wikibase-snakview-value' ).append( h );

			});
		});
	},

	/**
	 * Parse a quantity value
	 *
	 * @param dv
	 * @return { 'unit': unit, 'amount': amount } or null
	 */
	parseQuantity: function ( dv ) {
		if ( dv === undefined || dv.type !== 'quantity' ) return null;

		var unit = dv.value.unit;
		if ( unit === '1' ) return null;

		var slashPos = unit.lastIndexOf('/');
		if ( slashPos === -1 ) return null;

		unit = unit.substr( slashPos + 1 );

		var amount = dv.value.amount;
		if ( amount[0] === '+' ) amount = amount.substr(1);

		return { 'unit': unit, 'amount': amount };
	},

	/**
	 * Parse a date and return the year
	 *
	 * @param dv
	 * @return year or null
	 */
	parseDateToYear: function( dv ) {
		if ( dv === undefined ||
			dv.type !== 'time' ||
			dv.value.after !== 0 ||
			dv.value.before !== 0 ||
			dv.value.precision < 9) return null;

		var datetime = dv.value.time;

		if (datetime[0] === '+') datetime = datetime.substr(1);
		else if (datetime[0] === '-') return null; // BCE

		var parts = datetime.split('-');
		while (parts[0].length && parts[0][0] === '0') parts[0] = parts[0].substr(1);
		return parts[0];
	}

};

$( function() {
	var ns = mw.config.get( 'wgNamespaceNumber' );
	if ( ns !== 0 && ns !== 120 ) return;
	if (mw.config.get( 'wgAction' ) !== 'view') return;
	if (mw.config.get( 'wbIsEditView' ) === false) return;
	if (mw.config.get( 'wgIsRedirect' ) === true) return;

	Bamyers99.UnitsConverter.init() ;
} );