import os
import json
from datetime import datetime
from pathlib import Path
from collections import defaultdict
# ============================================================
# CONVERSION DATA
# ============================================================
CATEGORIES = {
# ── LENGTH ──────────────────────────────────────────────
"length": {
"base": "meter",
"units": {
"meter": 1,
"kilometer": 1000,
"centimeter": 0.01,
"millimeter": 0.001,
"micrometer": 1e-6,
"nanometer": 1e-9,
"mile": 1609.344,
"yard": 0.9144,
"foot": 0.3048,
"inch": 0.0254,
"nautical mile": 1852,
"light year": 9.461e15,
}
},
# ── WEIGHT / MASS ────────────────────────────────────────
"weight": {
"base": "kilogram",
"units": {
"kilogram": 1,
"gram": 0.001,
"milligram": 1e-6,
"microgram": 1e-9,
"metric ton": 1000,
"pound": 0.453592,
"ounce": 0.0283495,
"stone": 6.35029,
"quintal": 100,
"carat": 0.0002,
}
},
# ── TEMPERATURE ──────────────────────────────────────────
"temperature": {
"base": "celsius",
"units": {
"celsius": None, # special handling
"fahrenheit": None,
"kelvin": None,
"rankine": None,
}
},
# ── VOLUME ───────────────────────────────────────────────
"volume": {
"base": "liter",
"units": {
"liter": 1,
"milliliter": 0.001,
"cubic meter": 1000,
"cubic centimeter": 0.001,
"cubic inch": 0.0163871,
"cubic foot": 28.3168,
"gallon (US)": 3.78541,
"gallon (UK)": 4.54609,
"quart (US)": 0.946353,
"pint (US)": 0.473176,
"cup (US)": 0.236588,
"fluid ounce": 0.0295735,
"tablespoon": 0.0147868,
"teaspoon": 0.00492892,
}
},
# ── SPEED ────────────────────────────────────────────────
"speed": {
"base": "meter/second",
"units": {
"meter/second": 1,
"kilometer/hour": 0.277778,
"mile/hour": 0.44704,
"foot/second": 0.3048,
"knot": 0.514444,
"mach": 340.29,
"light speed": 299792458,
}
},
# ── AREA ─────────────────────────────────────────────────
"area": {
"base": "square meter",
"units": {
"square meter": 1,
"square kilometer": 1e6,
"square centimeter": 1e-4,
"square millimeter": 1e-6,
"square mile": 2589988.11,
"square yard": 0.836127,
"square foot": 0.092903,
"square inch": 6.4516e-4,
"hectare": 10000,
"acre": 4046.86,
"cent": 40.4686, # Indian unit
"bigha": 2508.38, # common Indian unit
}
},
# ── TIME ─────────────────────────────────────────────────
"time": {
"base": "second",
"units": {
"second": 1,
"millisecond": 0.001,
"microsecond": 1e-6,
"nanosecond": 1e-9,
"minute": 60,
"hour": 3600,
"day": 86400,
"week": 604800,
"month": 2628000, # avg 30.44 days
"year": 31536000,
"decade": 315360000,
"century": 3153600000,
}
},
# ── DATA / STORAGE ───────────────────────────────────────
"data": {
"base": "byte",
"units": {
"bit": 0.125,
"byte": 1,
"kilobyte": 1024,
"megabyte": 1024**2,
"gigabyte": 1024**3,
"terabyte": 1024**4,
"petabyte": 1024**5,
"kibibyte": 1024,
"mebibyte": 1024**2,
"gibibyte": 1024**3,
}
},
# ── PRESSURE ─────────────────────────────────────────────
"pressure": {
"base": "pascal",
"units": {
"pascal": 1,
"kilopascal": 1000,
"megapascal": 1e6,
"bar": 100000,
"millibar": 100,
"atmosphere": 101325,
"psi": 6894.76,
"mmHg": 133.322,
"torr": 133.322,
"inHg": 3386.39,
}
},
# ── ENERGY ───────────────────────────────────────────────
"energy": {
"base": "joule",
"units": {
"joule": 1,
"kilojoule": 1000,
"megajoule": 1e6,
"calorie": 4.184,
"kilocalorie": 4184,
"watt-hour": 3600,
"kilowatt-hour": 3.6e6,
"electronvolt": 1.602e-19,
"BTU": 1055.06,
"therm": 105480400,
"foot-pound": 1.35582,
}
},
# ── FUEL EFFICIENCY ──────────────────────────────────────
"fuel": {
"base": "km/l",
"units": {
"km/l": 1,
"l/100km": None, # inverse — special handling
"mpg (US)": 0.425144,
"mpg (UK)": 0.354006,
"miles/l": 1.60934,
}
},
}
# ============================================================
# HISTORY FILE
# ============================================================
HISTORY_FILE = "unit_converter_history.json"
def load_history():
if Path(HISTORY_FILE).exists():
try:
with open(HISTORY_FILE, "r") as f:
return json.load(f)
except:
pass
return []
def save_history(entry):
history = load_history()
history.append(entry)
history = history[-50:]
with open(HISTORY_FILE, "w") as f:
json.dump(history, f, indent=2)
# ============================================================
# CONVERSION LOGIC
# ============================================================
def convert_temperature(value, from_unit, to_unit):
"""Special handling for temperature conversions."""
# Convert to Celsius first
if from_unit == "celsius":
celsius = value
elif from_unit == "fahrenheit":
celsius = (value - 32) * 5 / 9
elif from_unit == "kelvin":
celsius = value - 273.15
elif from_unit == "rankine":
celsius = (value - 491.67) * 5 / 9
else:
return None
# Convert from Celsius to target
if to_unit == "celsius":
return celsius
elif to_unit == "fahrenheit":
return celsius * 9 / 5 + 32
elif to_unit == "kelvin":
return celsius + 273.15
elif to_unit == "rankine":
return (celsius + 273.15) * 9 / 5
return None
def convert_fuel(value, from_unit, to_unit):
"""Special handling for fuel efficiency (l/100km is inverse)."""
# Convert to km/l first
if from_unit == "l/100km":
kml = 100 / value if value != 0 else 0
else:
factor = CATEGORIES["fuel"]["units"].get(from_unit, 1)
kml = value * factor
# Convert from km/l to target
if to_unit == "l/100km":
return 100 / kml if kml != 0 else 0
else:
factor = CATEGORIES["fuel"]["units"].get(to_unit, 1)
return kml / factor
def convert(value, from_unit, to_unit, category):
"""
Universal conversion function.
Returns converted value or None.
"""
from_unit = from_unit.lower().strip()
to_unit = to_unit.lower().strip()
if from_unit == to_unit:
return value
cat_data = CATEGORIES.get(category)
if not cat_data:
return None
# Special categories
if category == "temperature":
return convert_temperature(value, from_unit, to_unit)
if category == "fuel":
return convert_fuel(value, from_unit, to_unit)
units = cat_data["units"]
if from_unit not in units or to_unit not in units:
return None
# Convert via base unit
value_in_base = value * units[from_unit]
result = value_in_base / units[to_unit]
return result
# ============================================================
# FORMAT RESULT
# ============================================================
def fmt_result(value):
"""Smart formatting: remove unnecessary decimals."""
if value is None:
return "N/A"
if abs(value) >= 1e12 or (abs(value) < 1e-6 and value != 0):
return f"{value:.6e}"
if value == int(value):
return f"{int(value):,}"
if abs(value) >= 100:
return f"{value:,.4f}"
if abs(value) >= 1:
return f"{value:.6f}".rstrip("0").rstrip(".")
return f"{value:.10f}".rstrip("0").rstrip(".")
# ============================================================
# DISPLAY UNIT LIST
# ============================================================
def list_units(category):
cat_data = CATEGORIES.get(category)
if not cat_data:
print(f" Unknown category: {category}")
return
units = list(cat_data["units"].keys())
print(f"\n Units in '{category}':")
print(" " + "-"*40)
for i, unit in enumerate(units, 1):
print(f" {i:>3}. {unit}")
print(" " + "-"*40)
# ============================================================
# QUICK CONVERT (ALL TO ONE)
# ============================================================
def convert_one_to_all(value, from_unit, category):
"""Show value converted to ALL units in the category."""
cat_data = CATEGORIES.get(category)
if not cat_data:
return
print(f"\n {fmt_result(value)} {from_unit} =")
print(" " + "-"*45)
for unit in cat_data["units"].keys():
if unit == from_unit.lower():
continue
result = convert(value, from_unit, unit, category)
if result is not None:
print(f" {fmt_result(result):<20} {unit}")
print(" " + "-"*45)
# ============================================================
# INTERACTIVE CONVERSION
# ============================================================
def do_convert(category):
list_units(category)
from_unit = input(f"\n From unit: ").strip().lower()
to_unit = input(f" To unit : ").strip().lower()
cat_data = CATEGORIES.get(category, {})
units = cat_data.get("units", {})
# Fuzzy match
def fuzzy_match(u, unit_list):
if u in unit_list:
return u
matches = [k for k in unit_list if u in k or k in u]
return matches[0] if matches else None
from_unit = fuzzy_match(from_unit, units) or from_unit
to_unit = fuzzy_match(to_unit, units) or to_unit
if from_unit not in units:
print(f" Unknown unit: {from_unit}")
return
if to_unit not in units:
print(f" Unknown unit: {to_unit}")
return
try:
value = float(input(f" Value : ").strip())
except ValueError:
print(" Invalid value.")
return
result = convert(value, from_unit, to_unit, category)
if result is None:
print(" Conversion failed.")
return
print(f"\n {'='*45}")
print(f" {fmt_result(value)} {from_unit}")
print(f" = {fmt_result(result)} {to_unit}")
print(f" {'='*45}")
# Show all option
show_all = input("\n Show conversion to ALL units? (y/n): ").strip().lower()
if show_all == "y":
convert_one_to_all(value, from_unit, category)
# Save to history
save_history({
"time": datetime.now().strftime("%d-%m-%Y %H:%M"),
"category": category,
"value": value,
"from": from_unit,
"result": result,
"to": to_unit,
})
# ============================================================
# QUICK CONVERTER (COMMON PAIRS)
# ============================================================
def quick_convert():
print("\n QUICK CONVERSIONS")
print(" " + "-"*40)
quick_pairs = [
("1", "km", "mile", "length"),
("2", "kg", "pound", "weight"),
("3", "celsius", "fahrenheit", "temperature"),
("4", "liter", "gallon (US)","volume"),
("5", "meter/second", "km/hour","speed"),
("6", "hectare", "acre", "area"),
("7", "gigabyte", "megabyte", "data"),
("8", "bar", "psi", "pressure"),
("9", "kilocalorie","joule", "energy"),
("10", "km/l", "mpg (US)", "fuel"),
]
for num, f, t, cat in quick_pairs:
result = convert(1, f, t, cat)
print(f" {num:>3}. 1 {f:<18} = {fmt_result(result)} {t}")
print(" " + "-"*40)
print("\n Enter a number to use that conversion,")
choice = input(" or press Enter to go back: ").strip()
if not choice:
return
# Find the pair
selected = None
for num, f, t, cat in quick_pairs:
if choice == num:
selected = (f, t, cat)
break
if not selected:
return
from_unit, to_unit, category = selected
try:
value = float(input(f"\n Enter value in {from_unit}: ").strip())
except ValueError:
print(" Invalid value.")
return
result = convert(value, from_unit, to_unit, category)
print(f"\n {fmt_result(value)} {from_unit} = {fmt_result(result)} {to_unit}")
save_history({
"time": datetime.now().strftime("%d-%m-%Y %H:%M"),
"category": category,
"value": value,
"from": from_unit,
"result": result,
"to": to_unit,
})
# ============================================================
# SHOW HISTORY
# ============================================================
def show_history():
history = load_history()
if not history:
print("\n No conversion history yet.")
return
print("\n" + "="*65)
print(f" CONVERSION HISTORY (last {len(history)})")
print("="*65)
print(f" {'TIME':<18} {'CAT':<14} {'FROM':<25} TO")
print(" " + "-"*60)
for h in reversed(history[-20:]):
from_str = f"{fmt_result(h['value'])} {h['from']}"
to_str = f"{fmt_result(h['result'])} {h['to']}"
print(f" {h['time']:<18} {h['category']:<14} "
f"{from_str:<25} {to_str}")
print("="*65)
# ============================================================
# MAIN MENU
# ============================================================
def print_menu():
print("\n" + "-"*50)
print(" UNIT CONVERTER")
print("-"*50)
print(" 1. Length")
print(" 2. Weight / Mass")
print(" 3. Temperature")
print(" 4. Volume")
print(" 5. Speed")
print(" 6. Area")
print(" 7. Time")
print(" 8. Data / Storage")
print(" 9. Pressure")
print(" 10. Energy")
print(" 11. Fuel Efficiency")
print(" 12. Quick conversions (common pairs)")
print(" 13. Conversion history")
print(" 0. Exit")
print("-"*50)
MENU_MAP = {
"1": "length",
"2": "weight",
"3": "temperature",
"4": "volume",
"5": "speed",
"6": "area",
"7": "time",
"8": "data",
"9": "pressure",
"10": "energy",
"11": "fuel",
}
def main():
print("\n" + "="*55)
print(" UNIT CONVERTER")
print("="*55)
print(f"\n {len(CATEGORIES)} categories | "
f"{sum(len(v['units']) for v in CATEGORIES.values())}+ units")
print(" Length, Weight, Temperature, Volume, Speed,")
print(" Area, Time, Data, Pressure, Energy, Fuel")
while True:
print_menu()
choice = input(" > ").strip()
if choice in MENU_MAP:
do_convert(MENU_MAP[choice])
elif choice == "12":
quick_convert()
elif choice == "13":
show_history()
elif choice == "0":
print("\n Goodbye!\n")
break
else:
print(" Invalid choice.")
# ============================================================
# RUN
# ============================================================
if __name__ == "__main__":
main()
No comments:
Post a Comment