I have an old iPhone 4s that I wished to repurpose as a clock/nightlight. I expanded it to retrieve the weather based on the user's ip address.
A Pen by Steven Estrella on CodePen.
I have an old iPhone 4s that I wished to repurpose as a clock/nightlight. I expanded it to retrieve the weather based on the user's ip address.
A Pen by Steven Estrella on CodePen.
<main id="container" class="daymode"> | |
<time id="date" datetime="" class="clocktext"></time> | |
<time id="time" datetime="" class="clocktext"></time> | |
<div id="weather" class="clocktext infotext"></div> | |
<img id="icon" src="https://openweathermap.org/img/w/01n.png" alt="weather icon"/> | |
<div id="gps" class="clocktext infotext"></div> | |
<div id="weatherdetails" class="clocktext infotext"></div> | |
<div id="ssmlink" class="clocktext infotext"> | |
<a href="https://shearspiremedia.com/demos/clock/" target="_blank">ShearSpire Media Weather Clock</a> | |
</div> | |
</main> | |
<!-- A simple clock that fits on an old iPhone 4s so you can reuse your old device as a wall clock. This version also includes ip location detection and displays the current weather. --> |
//NOTE: ES5 chosen instead of ES6 for compatibility with older devices | |
var now, dd, td, details; | |
var lat, lon, gd; | |
var weatherurl, wd, icon; | |
var city, region; | |
var temperaturescale = "F"; //set to F or C (fahrenheit or celsius) | |
var usephp = false; // set to true to use a php document to hide your api key | |
var locationRequested = false; | |
var weatherminute; | |
var months = ["January","February","March","April","May","June","July","August","September","October","November","December"]; | |
var days = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"]; | |
var sunsettime = 0; | |
var sunrisetime = 0; | |
var iconurl = "https://openweathermap.org/img/w/"; | |
var winddegrees = [[348.75,"N"],[326.25,"NNW"],[303.75,"NW"],[281.25,"WNW"],[258.75,"W"],[236.25,"WSW"],[213.75,"SW"],[191.25,"SSW"],[168.75,"S"],[146.25,"SSE"],[123.75,"SE"],[101.25,"ESE"],[78.75,"E"],[56.25,"ENE"],[33.75,"NE"],[11.25,"NNE"],[0,"N"]]; | |
document.addEventListener("DOMContentLoaded", init, false); | |
function init(){ | |
//a set of custom icons I created | |
iconurl = "https://shearspiremedia.com/demos/icons4owm/"; | |
dd = document.getElementById("date"); | |
td = document.getElementById("time"); | |
wd = document.getElementById("weather"); | |
gd = document.getElementById("gps"); | |
icon = document.getElementById("icon"); | |
details = document.getElementById("weatherdetails"); | |
weatherminute = randRange(0,14); | |
getLocation(); | |
updateTime(); | |
setInterval(updateTime,1000); | |
} | |
function updateTime(){ | |
var clockdata = getClockStrings(); | |
dd.innerHTML = clockdata.datehtml; | |
td.innerHTML = clockdata.timehtml; | |
dd.dateTime = now.toISOString(); | |
td.dateTime = now.toISOString(); | |
var sec = now.getSeconds(); | |
var minutes = now.getMinutes(); | |
if (locationRequested && sec === 0){ | |
checkForSunset(); //checks for sunset once each minute | |
if (minutes % 15 === weatherminute){ | |
getWeather(); //get weather every 15 minutes | |
//weatherminute is a random number between | |
//0 and 14 to ensure that users don't all hit | |
//the API at the same minute | |
} | |
} | |
} | |
function getClockStrings(){ | |
now = new Date(); | |
var year = now.getFullYear(); | |
var month = months[now.getMonth()]; | |
var date = now.getDate(); | |
var day = days[now.getDay()]; | |
var hour = now.getHours(); | |
var minutes = now.getMinutes(); | |
var seconds = now.getSeconds(); | |
var meridian = hour < 12 ? "AM" : "PM"; | |
var clockhour = hour > 12 ? hour - 12 : hour; | |
if (hour === 0) {clockhour = 12;} | |
var clockminutes = minutes < 10 ? "0" + minutes : minutes; | |
var clockseconds = seconds < 10 ? "0" + seconds : seconds; | |
var datehtml = day + ", " + month + " " + date + ", " + year; | |
var timehtml = clockhour + ":" + clockminutes + "<span>:" + clockseconds + " " + meridian + "</span>"; | |
return {"datehtml":datehtml,"timehtml":timehtml}; | |
} | |
function getLocation() { | |
var xhttp = new XMLHttpRequest(); | |
xhttp.onreadystatechange = function() { | |
if (this.readyState === 4) { | |
var noerror = true;//for testing | |
if (this.status === 200 && noerror){ | |
var data = xhttp.responseText; | |
showPosition(JSON.parse(data)); | |
}else{ | |
showPosition(null); | |
} | |
} | |
}; | |
xhttp.open("GET", "https://extreme-ip-lookup.com/json/", true); | |
xhttp.send(); | |
} | |
function showPosition(position) { | |
if (!position){ | |
gd.innerHTML = "IP address location service is unavailable."; | |
return; | |
} | |
lat = Number(position.lat); | |
lon = Number(position.lon); | |
city = position.city; | |
region = position.region; | |
//gd.innerHTML = "GPS: " + lat.toFixed(2) + " | " + lon.toFixed(2); | |
gd.innerHTML = city + ", " + region; | |
if (usephp){ | |
weatherurl = "clock.php?lat=" + lat + "&lon=" + lon; | |
//weatherurl = "clock.php?lat=200&lon=200"; // for testing error response | |
}else{ | |
weatherurl = "https://api.openweathermap.org/data/2.5/weather?"; | |
weatherurl += "lat=" + lat + "&lon=" + lon + "&APPID="; | |
weatherurl += YOUR_API_KEY_HERE; | |
//for the APPID, please substitute your own API Key you can get for free from openweathermap.org | |
} | |
/* | |
an alternative to exposing your API Key is to call a PHP document | |
where the API Key is stored securely on the server. The PHP document in turn | |
calls the weatherurl and returns an HTML document whose body is a json string that | |
can be parsed. Codepen doesn't allow php access so I established a throw-away | |
account on openweathermap.org for this demonstration which has the apikey referenced here. | |
for a working example that uses PHP to hide the api key see | |
https://shearspiremedia.com/demos/clock/ | |
*/ | |
if (!locationRequested){ | |
getWeather(); | |
locationRequested = true; | |
} | |
} | |
function getWeather(){ | |
wd.innerHTML = "getting weather"; | |
// I opted to use the older XMLHttpRequest because fetch is not supported on old devices like the iPhone 4s | |
// I developed this page so I could use my old iPhone 4s as a wall clock. | |
var xhttp = new XMLHttpRequest(); | |
xhttp.responseType = usephp ? "document" : "text"; //the php file returns a document rather than plain text | |
xhttp.onreadystatechange = function() { | |
if (this.readyState === 4 && this.status === 200) { | |
//when using PHP as a data source we need the textContent of the body of the returned document | |
var data = usephp ? xhttp.response.body.textContent : xhttp.responseText; | |
processWeather(JSON.parse(data)); | |
} | |
}; | |
xhttp.open("GET", weatherurl, true); | |
xhttp.send(); | |
} | |
function convertTemperature(kelvin){ | |
//converts temps in kelvin to celsius or fahrenheit | |
var celsius = (kelvin - 273.15); | |
return temperaturescale === "F" ? celsius * 1.8 + 32 : celsius; | |
} | |
function processWeather(data){ | |
var weather = data["weather"][0]; | |
icon.src = iconurl + weather.icon + ".png"; | |
icon.style.opacity = 1; | |
var localtemperature = convertTemperature(data["main"].temp).toFixed(0); | |
wd.innerHTML = localtemperature + "°" + temperaturescale + " " + weather.description; | |
sunsettime = Number(data["sys"].sunset); | |
sunrisetime = Number(data["sys"].sunrise); | |
checkForSunset(); | |
var gpsline = "GPS: " + lat.toFixed(2) + " | " + lon.toFixed(2) + "<br>"; | |
var pressureline = "Pressure: " + data.main.pressure + " hpa<br>"; | |
var humidityline = "Humidity: " + data.main.humidity + "%<br>"; | |
var windline = "Winds: " + data.wind.speed + " mph "; | |
if (data.wind.deg){ | |
windline += Math.round(data.wind.deg) + "°(" + getWindDirection(data.wind.deg) + ")"; | |
} | |
windline += "<br>"; | |
var sunriseline = "Sunrise: " + new Date(data.sys.sunrise * 1000).toLocaleTimeString() + "<br>"; | |
var sunsetline = "Sunset: " + new Date(data.sys.sunset * 1000).toLocaleTimeString() + "<br>"; | |
details.innerHTML = windline + pressureline + humidityline + sunriseline + sunsetline + gpsline; | |
} | |
function checkForSunset(){ | |
var nowtime = now.getTime()/1000; | |
//changes the presentation style if the time of day is after sunset | |
//or before the next day's sunrise | |
var isDark = nowtime > sunsettime || nowtime < sunrisetime; | |
document.getElementById("container").className = isDark ? "nightmode" : "daymode"; | |
//uncomment the following if you want santa mode | |
// if (now.getMonth() === 11 && now.getDate() < 26){ | |
// document.getElementById("container").className = "santamode"; | |
// } | |
} | |
//random number utility function | |
function randRange(min, max) { | |
return Math.floor(Math.random()*(max-min+1))+min; | |
} | |
function getWindDirection(deg){ | |
for (var i=0;i<winddegrees.length;i++){ | |
if (deg > winddegrees[i][0]){ | |
return winddegrees[i][1]; | |
} | |
} | |
return "__"; | |
} | |
<script src="https://shearspiremedia.com/demos/clock/yak.js"></script> |
html {font-size:16px;} | |
body { | |
padding:0; | |
margin:0; | |
font-family:TrebuchetMS,Arial,sans-serif; | |
} | |
#container { | |
margin:0 auto; | |
padding:0; | |
width:100vw; | |
height:100vh; | |
text-align:center; | |
overflow:auto; | |
} | |
.nightmode { | |
background-color:#121212; | |
background-image: linear-gradient(to bottom left, #121212 10%,#333955 100%); | |
color:#fff; | |
text-shadow:1px 1px 1px black; | |
} | |
.daymode { | |
background-color: #87ceeb; | |
background-image: linear-gradient(to bottom left, #87ceeb 0%,#fff 100%); | |
color:#333; | |
text-shadow:1px 1px 10px white; | |
} | |
.santamode { | |
background-color: #0f3; | |
background-image: linear-gradient(to bottom left, #0f3 0%,#fff 100%); | |
color:#c00; | |
text-shadow:1px 1px 10px white; | |
} | |
.clocktext { | |
display:block; | |
margin:0; | |
padding:1px 0 0 0; | |
width:100%; | |
text-align:center; | |
line-height:1.0; | |
white-space:nowrap; | |
} | |
#date { | |
font-size:1.3rem; | |
padding-top:15px; | |
} | |
#time { | |
font-size:5rem; | |
margin:1px 0 0 0; | |
} | |
#time span { | |
display:inline-block; | |
padding:0; | |
vertical-align: baseline; | |
text-align:left; | |
width: 2em; | |
margin:0 0 0 0.5em; | |
font-size:1.5rem; | |
line-height:1.5; | |
white-space:normal; | |
} | |
.infotext { | |
margin:0; | |
padding:0 5px 0 5px; | |
font-size:1.3rem; | |
line-height:1.4; | |
width:auto; | |
} | |
#weather {display:block;width:auto;} | |
#weatherdetails {margin:50px 0 20px 0;} | |
#icon { | |
display:inline-block; | |
opacity:0; | |
vertical-align:top; | |
height:50px;width:50px; | |
} | |
#gpsbutton { | |
-webkit-appearance: none; | |
-moz-appearance: none; | |
display:block; | |
margin:0 auto; | |
padding:0 16px 0 16px; | |
width:auto; | |
height:40px; | |
border:2px outset #fff; | |
border-radius:12px; | |
background:#dedeff; | |
color:black; | |
font-size:20px; | |
cursor:pointer; | |
} | |
#gpsbutton:hover { | |
background:#000033; | |
border:2px inset #fff; | |
color:#fff; | |
} | |
@media (min-width: 480px){ | |
#date {font-size:2rem;} | |
#time {font-size:8rem;} | |
#time span { | |
font-size:2rem; | |
line-height: 2; | |
} | |
.infotext { | |
font-size:1.8rem; | |
} | |
#weather {display:inline-block;} | |
} | |
@media (min-width: 1000px){ | |
#icon {height:100px;width:100px;} | |
#date {font-size:4rem;} | |
#time {font-size:16rem;} | |
#time span { | |
font-size:4rem; | |
line-height: 2; | |
} | |
.infotext { | |
font-size:3.6rem; | |
} | |
#weather {display:inline-block;} | |
} | |
#ssmlink {margin-bottom:30px;} | |
#ssmlink a:link, #ssmlink a:visited {color:#090;font-size:80%;} | |
#ssmlink a:hover {color:#0f0;} |