mirror of
https://github.com/mfactory-osaka/ESPTimeCast.git
synced 2026-02-19 11:54:56 -05:00
Added "Show Date" display mode
Added "Show Date" display mode
This commit is contained in:
@@ -13,9 +13,10 @@
|
||||
#include <time.h>
|
||||
#include <WiFiClientSecure.h>
|
||||
|
||||
#include "mfactoryfont.h" // Custom font
|
||||
#include "tz_lookup.h" // Timezone lookup, do not duplicate mapping here!
|
||||
#include "days_lookup.h" // Languages for the Days of the Week
|
||||
#include "mfactoryfont.h" // Custom font
|
||||
#include "tz_lookup.h" // Timezone lookup, do not duplicate mapping here!
|
||||
#include "days_lookup.h" // Languages for the Days of the Week
|
||||
#include "months_lookup.h" // Languages for the Months of the Year
|
||||
|
||||
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
|
||||
#define MAX_DEVICES 4
|
||||
@@ -49,6 +50,7 @@ int brightness = 7;
|
||||
bool flipDisplay = false;
|
||||
bool twelveHourToggle = false;
|
||||
bool showDayOfWeek = true;
|
||||
bool showDate = false;
|
||||
bool showHumidity = false;
|
||||
bool colonBlinkEnabled = true;
|
||||
char ntpServer1[64] = "pool.ntp.org";
|
||||
@@ -168,6 +170,7 @@ void loadConfig() {
|
||||
doc[F("flipDisplay")] = flipDisplay;
|
||||
doc[F("twelveHourToggle")] = twelveHourToggle;
|
||||
doc[F("showDayOfWeek")] = showDayOfWeek;
|
||||
doc[F("showDate")] = false;
|
||||
doc[F("showHumidity")] = showHumidity;
|
||||
doc[F("colonBlinkEnabled")] = colonBlinkEnabled;
|
||||
doc[F("ntpServer1")] = ntpServer1;
|
||||
@@ -233,8 +236,10 @@ void loadConfig() {
|
||||
flipDisplay = doc["flipDisplay"] | false;
|
||||
twelveHourToggle = doc["twelveHourToggle"] | false;
|
||||
showDayOfWeek = doc["showDayOfWeek"] | true;
|
||||
showDate = doc["showDate"] | false;
|
||||
showHumidity = doc["showHumidity"] | false;
|
||||
colonBlinkEnabled = doc.containsKey("colonBlinkEnabled") ? doc["colonBlinkEnabled"].as<bool>() : true;
|
||||
showWeatherDescription = doc["showWeatherDescription"] | false;
|
||||
|
||||
String de = doc["dimmingEnabled"].as<String>();
|
||||
dimmingEnabled = (de == "true" || de == "on" || de == "1");
|
||||
@@ -253,10 +258,6 @@ void loadConfig() {
|
||||
else
|
||||
tempSymbol = '[';
|
||||
|
||||
if (doc.containsKey("showWeatherDescription"))
|
||||
showWeatherDescription = doc["showWeatherDescription"];
|
||||
else
|
||||
showWeatherDescription = false;
|
||||
|
||||
// --- COUNTDOWN CONFIG LOADING ---
|
||||
if (doc.containsKey("countdown")) {
|
||||
@@ -408,19 +409,39 @@ void connectWiFi() {
|
||||
// Time / NTP Functions
|
||||
// -----------------------------------------------------------------------------
|
||||
void setupTime() {
|
||||
// sntp_stop();
|
||||
if (!isAPMode) {
|
||||
Serial.println(F("[TIME] Starting NTP sync"));
|
||||
}
|
||||
configTime(0, 0, ntpServer1, ntpServer2);
|
||||
setenv("TZ", ianaToPosix(timeZone), 1);
|
||||
tzset();
|
||||
ntpState = NTP_SYNCING;
|
||||
ntpStartTime = millis();
|
||||
ntpRetryCount = 0;
|
||||
ntpSyncSuccessful = false;
|
||||
|
||||
bool serverOk = false;
|
||||
IPAddress resolvedIP;
|
||||
|
||||
// Try first server if it's not empty
|
||||
if (strlen(ntpServer1) > 0 && WiFi.hostByName(ntpServer1, resolvedIP) == 1) {
|
||||
serverOk = true;
|
||||
}
|
||||
// Try second server if first failed
|
||||
else if (strlen(ntpServer2) > 0 && WiFi.hostByName(ntpServer2, resolvedIP) == 1) {
|
||||
serverOk = true;
|
||||
}
|
||||
|
||||
if (serverOk) {
|
||||
configTime(0, 0, ntpServer1, ntpServer2); // safe to call now
|
||||
setenv("TZ", ianaToPosix(timeZone), 1);
|
||||
tzset();
|
||||
ntpState = NTP_SYNCING;
|
||||
ntpStartTime = millis();
|
||||
ntpRetryCount = 0;
|
||||
ntpSyncSuccessful = false;
|
||||
} else {
|
||||
Serial.println(F("[TIME] NTP server lookup failed — skipping sync"));
|
||||
ntpSyncSuccessful = false;
|
||||
ntpState = NTP_IDLE; // or custom NTP_ERROR state
|
||||
// Trigger your error display here if desired
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Utility
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -454,6 +475,8 @@ void printConfigToSerial() {
|
||||
Serial.println(twelveHourToggle ? "Yes" : "No");
|
||||
Serial.print(F("Show Day of the Week: "));
|
||||
Serial.println(showDayOfWeek ? "Yes" : "No");
|
||||
Serial.print(F("Show Date: "));
|
||||
Serial.println(showDate ? "Yes" : "No");
|
||||
Serial.print(F("Show Weather Description: "));
|
||||
Serial.println(showWeatherDescription ? "Yes" : "No");
|
||||
Serial.print(F("Show Humidity: "));
|
||||
@@ -550,6 +573,7 @@ void setupWebServer() {
|
||||
else if (n == "flipDisplay") doc[n] = (v == "true" || v == "on" || v == "1");
|
||||
else if (n == "twelveHourToggle") doc[n] = (v == "true" || v == "on" || v == "1");
|
||||
else if (n == "showDayOfWeek") doc[n] = (v == "true" || v == "on" || v == "1");
|
||||
else if (n == "showDate") doc[n] = (v == "true" || v == "on" || v == "1");
|
||||
else if (n == "showHumidity") doc[n] = (v == "true" || v == "on" || v == "1");
|
||||
else if (n == "colonBlinkEnabled") doc[n] = (v == "true" || v == "on" || v == "1");
|
||||
else if (n == "dimStartHour") doc[n] = v.toInt();
|
||||
@@ -779,6 +803,17 @@ void setupWebServer() {
|
||||
request->send(200, "application/json", "{\"ok\":true}");
|
||||
});
|
||||
|
||||
server.on("/set_showdate", HTTP_POST, [](AsyncWebServerRequest *request) {
|
||||
bool showDateVal = false;
|
||||
if (request->hasParam("value", true)) {
|
||||
String v = request->getParam("value", true)->value();
|
||||
showDateVal = (v == "1" || v == "true" || v == "on");
|
||||
}
|
||||
showDate = showDateVal;
|
||||
Serial.printf("[WEBSERVER] Set showDate to %d\n", showDate);
|
||||
request->send(200, "application/json", "{\"ok\":true}");
|
||||
});
|
||||
|
||||
server.on("/set_humidity", HTTP_POST, [](AsyncWebServerRequest *request) {
|
||||
bool showHumidityNow = false;
|
||||
if (request->hasParam("value", true)) {
|
||||
@@ -1249,8 +1284,11 @@ void advanceDisplayMode() {
|
||||
String ntpField = String(ntpServer2);
|
||||
bool nightscoutConfigured = ntpField.startsWith("https://");
|
||||
|
||||
if (displayMode == 0) { // Clock -> ...
|
||||
if (weatherAvailable && (strlen(openWeatherApiKey) == 32) && (strlen(openWeatherCity) > 0) && (strlen(openWeatherCountry) > 0)) {
|
||||
if (displayMode == 0) { // Clock
|
||||
if (showDate) {
|
||||
displayMode = 5; // Date mode right after Clock
|
||||
Serial.println(F("[DISPLAY] Switching to display mode: DATE (from Clock)"));
|
||||
} else if (weatherAvailable && (strlen(openWeatherApiKey) == 32) && (strlen(openWeatherCity) > 0) && (strlen(openWeatherCountry) > 0)) {
|
||||
displayMode = 1;
|
||||
Serial.println(F("[DISPLAY] Switching to display mode: WEATHER (from Clock)"));
|
||||
} else if (countdownEnabled && !countdownFinished && ntpSyncSuccessful) {
|
||||
@@ -1263,7 +1301,21 @@ void advanceDisplayMode() {
|
||||
displayMode = 0;
|
||||
Serial.println(F("[DISPLAY] Staying in CLOCK (from Clock)"));
|
||||
}
|
||||
} else if (displayMode == 1) { // Weather -> ...
|
||||
} else if (displayMode == 5) { // Date mode
|
||||
if (weatherAvailable && (strlen(openWeatherApiKey) == 32) && (strlen(openWeatherCity) > 0) && (strlen(openWeatherCountry) > 0)) {
|
||||
displayMode = 1;
|
||||
Serial.println(F("[DISPLAY] Switching to display mode: WEATHER (from Date)"));
|
||||
} else if (countdownEnabled && !countdownFinished && ntpSyncSuccessful) {
|
||||
displayMode = 3;
|
||||
Serial.println(F("[DISPLAY] Switching to display mode: COUNTDOWN (from Date, weather skipped)"));
|
||||
} else if (nightscoutConfigured) {
|
||||
displayMode = 4;
|
||||
Serial.println(F("[DISPLAY] Switching to display mode: NIGHTSCOUT (from Date, weather & countdown skipped)"));
|
||||
} else {
|
||||
displayMode = 0;
|
||||
Serial.println(F("[DISPLAY] Switching to display mode: CLOCK (from Date)"));
|
||||
}
|
||||
} else if (displayMode == 1) { // Weather
|
||||
if (showWeatherDescription && weatherAvailable && weatherDescription.length() > 0) {
|
||||
displayMode = 2;
|
||||
Serial.println(F("[DISPLAY] Switching to display mode: DESCRIPTION (from Weather)"));
|
||||
@@ -1277,7 +1329,7 @@ void advanceDisplayMode() {
|
||||
displayMode = 0;
|
||||
Serial.println(F("[DISPLAY] Switching to display mode: CLOCK (from Weather)"));
|
||||
}
|
||||
} else if (displayMode == 2) { // Weather Description -> ...
|
||||
} else if (displayMode == 2) { // Weather Description
|
||||
if (countdownEnabled && !countdownFinished && ntpSyncSuccessful) {
|
||||
displayMode = 3;
|
||||
Serial.println(F("[DISPLAY] Switching to display mode: COUNTDOWN (from Description)"));
|
||||
@@ -2042,5 +2094,93 @@ void loop() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//DATE Display Mode
|
||||
else if (displayMode == 5 && showDate) {
|
||||
|
||||
// --- VALID DATE CHECK ---
|
||||
if (timeinfo.tm_year < 120 || timeinfo.tm_mday <= 0 || timeinfo.tm_mon < 0 || timeinfo.tm_mon > 11) {
|
||||
advanceDisplayMode();
|
||||
return; // skip drawing
|
||||
}
|
||||
// -------------------------
|
||||
String dateString;
|
||||
|
||||
// Get localized month names
|
||||
const char *const *months = getMonthsOfYear(language);
|
||||
String monthAbbr = String(months[timeinfo.tm_mon]).substring(0, 5);
|
||||
monthAbbr.toLowerCase();
|
||||
|
||||
// Add spaces between day digits
|
||||
String dayString = String(timeinfo.tm_mday);
|
||||
String spacedDay = "";
|
||||
for (size_t i = 0; i < dayString.length(); i++) {
|
||||
spacedDay += dayString[i];
|
||||
if (i < dayString.length() - 1) spacedDay += " ";
|
||||
}
|
||||
|
||||
// Function to check if day should come first for given language
|
||||
auto isDayFirst = [](const String &lang) {
|
||||
// Languages with DD-MM order
|
||||
const char *dayFirstLangs[] = {
|
||||
"af", // Afrikaans
|
||||
"cs", // Czech
|
||||
"da", // Danish
|
||||
"de", // German
|
||||
"eo", // Esperanto
|
||||
"es", // Spanish
|
||||
"et", // Estonian
|
||||
"fi", // Finnish
|
||||
"fr", // French
|
||||
"hr", // Croatian
|
||||
"hu", // Hungarian
|
||||
"it", // Italian
|
||||
"lt", // Lithuanian
|
||||
"lv", // Latvian
|
||||
"nl", // Dutch
|
||||
"no", // Norwegian
|
||||
"pl", // Polish
|
||||
"pt", // Portuguese
|
||||
"ro", // Romanian
|
||||
"sk", // Slovak
|
||||
"sl", // Slovenian
|
||||
"sr", // Serbian
|
||||
"sv", // Swedish
|
||||
"sw", // Swahili
|
||||
"tr" // Turkish
|
||||
};
|
||||
for (auto lf : dayFirstLangs) {
|
||||
if (lang.equalsIgnoreCase(lf)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
String langForDate = String(language);
|
||||
|
||||
if (langForDate == "ja") {
|
||||
// Japanese: month number (spaced digits) + day + symbol
|
||||
String spacedMonth = "";
|
||||
String monthNum = String(timeinfo.tm_mon + 1);
|
||||
dateString = monthAbbr + " " + spacedDay + " ±";
|
||||
|
||||
} else {
|
||||
if (isDayFirst(language)) {
|
||||
dateString = spacedDay + " " + monthAbbr;
|
||||
} else {
|
||||
dateString = monthAbbr + " " + spacedDay;
|
||||
}
|
||||
}
|
||||
|
||||
P.setTextAlignment(PA_CENTER);
|
||||
P.setCharSpacing(0);
|
||||
P.print(dateString);
|
||||
|
||||
if (millis() - lastSwitch > weatherDuration) {
|
||||
advanceDisplayMode();
|
||||
}
|
||||
}
|
||||
|
||||
yield();
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
"ntpServer2": "time.nist.gov",
|
||||
"twelveHourToggle": false,
|
||||
"showDayOfWeek": true,
|
||||
"showDate": false,
|
||||
"showHumidity": false,
|
||||
"colonBlinkEnabled": true,
|
||||
"language": "en",
|
||||
|
||||
@@ -322,7 +322,7 @@ textarea::placeholder {
|
||||
<button type="button" class="primary-button" id="geo-button" onclick="getLocation()" style="margin-top: 1rem;">Get My Location</button>
|
||||
|
||||
<div class="small">
|
||||
<strong>Location format examples:</strong> City, Country Code - Osaka, JP | ZIP,Country Code - 94040, US | Latitude, Longitude - 34.6937, 135.5023
|
||||
<strong>Location format examples:</strong> City, Country Code - Osaka, JP | ZIP, Country Code - 94040, US | Latitude, Longitude - 34.6937, 135.5023
|
||||
</div>
|
||||
<div class="geo-note" style="display: none;">
|
||||
<br>
|
||||
@@ -495,6 +495,14 @@ textarea::placeholder {
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label style="display: flex; align-items: center; margin-top: 1.75rem; justify-content: space-between;">
|
||||
<span style="margin-right: 0.5em;">Show Date:</span>
|
||||
<span class="toggle-switch">
|
||||
<input type="checkbox" id="showDate" name="showDate" onchange="setShowDate(this.checked)">
|
||||
<span class="toggle-slider"></span>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label style="display: flex; align-items: center; margin-top: 1.75rem; justify-content: space-between;">
|
||||
<span style="margin-right: 0.5em;">Display 12-hour Clock:</span>
|
||||
<span class="toggle-switch">
|
||||
@@ -655,6 +663,7 @@ window.onload = function () {
|
||||
document.getElementById('ntpServer2').value = data.ntpServer2 || "";
|
||||
document.getElementById('twelveHourToggle').checked = !!data.twelveHourToggle;
|
||||
document.getElementById('showDayOfWeek').checked = !!data.showDayOfWeek;
|
||||
document.getElementById('showDate').checked = !!data.showDate;
|
||||
document.getElementById('showHumidity').checked = !!data.showHumidity;
|
||||
document.getElementById('colonBlinkEnabled').checked = !!data.colonBlinkEnabled;
|
||||
document.getElementById('showWeatherDescription').checked = !!data.showWeatherDescription;
|
||||
@@ -796,6 +805,7 @@ async function submitConfig(event) {
|
||||
formData.set('flipDisplay', document.getElementById('flipDisplay').checked ? 'on' : '');
|
||||
formData.set('twelveHourToggle', document.getElementById('twelveHourToggle').checked ? 'on' : '');
|
||||
formData.set('showDayOfWeek', document.getElementById('showDayOfWeek').checked ? 'on' : '');
|
||||
formData.set('showDate', document.getElementById('showDate').checked ? 'on' : '');
|
||||
formData.set('showHumidity', document.getElementById('showHumidity').checked ? 'on' : '');
|
||||
formData.set('colonBlinkEnabled', document.getElementById('colonBlinkEnabled').checked ? 'on' : '');
|
||||
|
||||
@@ -1098,6 +1108,14 @@ function setShowDayOfWeek(val) {
|
||||
});
|
||||
}
|
||||
|
||||
function setShowDate(val) {
|
||||
fetch('/set_showdate', {
|
||||
method: 'POST',
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: "value=" + (val ? 1 : 0)
|
||||
});
|
||||
}
|
||||
|
||||
function setColonBlink(val) {
|
||||
fetch('/set_colon_blink', {
|
||||
method: 'POST',
|
||||
|
||||
50
ESPTimeCast_ESP32/months_lookup.h
Normal file
50
ESPTimeCast_ESP32/months_lookup.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#ifndef MONTHS_LOOKUP_H
|
||||
#define MONTHS_LOOKUP_H
|
||||
|
||||
typedef struct {
|
||||
const char* lang;
|
||||
const char* months[12]; // Jan to Dec
|
||||
} MonthsMapping;
|
||||
|
||||
const MonthsMapping months_mappings[] = {
|
||||
{ "af", { "j&a&n", "f&e&b", "m&a&r", "a&p&r", "m&e&i", "j&u&n", "j&u&l", "a&u&g", "s&e&p", "o&k&t", "n&o&v", "d&e&s" } }, // Afrikaans
|
||||
{ "cs", { "l&e&d", "u&n&o", "b&r&e", "d&u&b", "k&v&e", "c&e&r", "c&v&c", "s&r&p", "z&a&r", "r&i&j", "l&i&s", "p&r&o" } }, // Czech
|
||||
{ "da", { "j&a&n", "f&e&b", "m&a&r", "a&p&r", "m&a&j", "j&u&n", "j&u&l", "a&u&g", "s&e&p", "o&k&t", "n&o&v", "d&e&c" } }, // Danish
|
||||
{ "de", { "j&a&n", "f&e&b", "m&a&r", "a&p&r", "m&a&i", "j&u&n", "j&u&l", "a&u&g", "s&e&p", "o&k&t", "n&o&v", "d&e&z" } }, // German
|
||||
{ "en", { "j&a&n", "f&e&b", "m&a&r", "a&p&r", "m&a&y", "j&u&n", "j&u&l", "a&u&g", "s&e&p", "o&c&t", "n&o&v", "d&e&c" } }, // English
|
||||
{ "eo", { "j&a&n", "f&e&b", "m&a&r", "a&p&r", "m&a&j", "j&u&n", "j&u&l", "a&u&g", "s&e&p", "o&k&t", "n&o&v", "d&e&c" } }, // Esperanto
|
||||
{ "es", { "e&n&e", "f&e&b", "m&a&r", "a&b&r", "m&a&y", "j&u&n", "j&u&l", "a&g&o", "s&e&p", "o&c&t", "n&o&v", "d&i&c" } }, // Spanish
|
||||
{ "et", { "j&a&n", "v&e&b", "m&a&r", "a&p&r", "m&a&i", "j&u&n", "j&u&l", "a&u&g", "s&e&p", "o&k&t", "n&o&v", "d&e&t" } }, // Estonian
|
||||
{ "fi", { "t&a&m", "h&e&l", "m&a&a", "h&u&h", "t&o&u", "k&e&s", "h&e&i", "e&l&o", "s&y&y", "l&o&k", "m&a&r", "j&o&u" } }, // Finnish
|
||||
{ "fr", { "j&a&n", "f&e&v", "m&a&r", "a&v&r", "m&a&i", "j&u&n", "j&u&l", "a&o&u", "s&e&p", "o&c&t", "n&o&v", "d&e&c" } }, // French
|
||||
{ "hr", { "s&i&j", "v&e&l", "o&z&u", "t&r&a", "s&v&i", "l&i&p", "s&r&p", "k&o&l", "r&u&j", "l&i&s", "s&t&u", "p&r&o" } }, // Croatian
|
||||
{ "hu", { "j&a&n", "f&e&b", "m&a&r", "a&p&r", "m&a&j", "j&u&n", "j&u&l", "a&u&g", "s&z&e", "o&k&t", "n&o&v", "d&e&c" } }, // Hungarian
|
||||
{ "it", { "g&e&n", "f&e&b", "m&a&r", "a&p&r", "m&a&g", "g&i&u", "l&u&g", "a&g&o", "s&e&t", "o&t&t", "n&o&v", "d&i&c" } }, // Italian
|
||||
{ "ja", { "1 ²", "2 ²", "3 ²", "4 ²", "5 ²", "6 ²", "7 ²", "8 ²", "9 ²", "10 ²", "11 ²", "12 ²" } }, // Japanese
|
||||
{ "lt", { "s&a&u", "v&a&s", "k&o&v", "b&a&l", "g&e&g", "b&i&r", "l&i&e", "r&u&g", "s&w&e", "s&p&a", "l&a&p", "g&r&u" } }, // Lithuanian
|
||||
{ "lv", { "j&a&n", "f&e&b", "m&a&r", "a&p&r", "m&a&i", "j&u&n", "j&u&l", "a&u&g", "s&e&p", "o&k&t", "n&o&v", "d&e&c" } }, // Latvian
|
||||
{ "nl", { "j&a&n", "f&e&b", "m&a&a", "a&p&r", "m&e&i", "j&u&n", "j&u&l", "a&u&g", "s&e&p", "o&k&t", "n&o&v", "d&e&c" } }, // Dutch
|
||||
{ "no", { "j&a&n", "f&e&b", "m&a&r", "a&p&r", "m&a&i", "j&u&n", "j&u&l", "a&u&g", "s&e&p", "o&k&t", "n&o&v", "d&e&s" } }, // Norwegian
|
||||
{ "pl", { "s&t&y", "l&u&t", "m&a&r", "k&w&i", "m&a&j", "c&z&e", "l&i&p", "s&i&e", "w&r&z", "p&a&z", "l&i&s", "g&r&u" } }, // Polish
|
||||
{ "pt", { "j&a&n", "f&e&v", "m&a&r", "a&b&r", "m&a&i", "j&u&n", "j&u&l", "a&g&o", "s&e&t", "o&u&t", "n&o&v", "d&e&z" } }, // Portuguese
|
||||
{ "ro", { "i&a&n", "f&e&b", "m&a&r", "a&p&r", "m&a&i", "i&u&n", "i&u&l", "a&u&g", "s&e&p", "o&c&t", "n&o&v", "d&e&c" } }, // Romanian
|
||||
{ "sk", { "j&a&n", "f&e&b", "m&a&r", "a&p&r", "m&a&j", "j&u&n", "j&u&l", "a&u&g", "s&e&p", "o&k&t", "n&o&v", "d&e&c" } }, // Slovak
|
||||
{ "sl", { "j&a&n", "f&e&b", "m&a&r", "a&p&r", "m&a&j", "j&u&n", "j&u&l", "a&v&g", "s&e&p", "o&k&t", "n&o&v", "d&e&c" } }, // Slovenian
|
||||
{ "sr", { "j&a&n", "f&e&b", "m&a&r", "a&p&r", "m&a&j", "j&u&n", "j&u&l", "a&v&g", "s&e&p", "o&k&t", "n&o&v", "d&e&c" } }, // Serbian
|
||||
{ "sv", { "j&a&n", "f&e&b", "m&a&r", "a&p&r", "m&a&j", "j&u&n", "j&u&l", "a&u&g", "s&e&p", "o&k&t", "n&o&v", "d&e&c" } }, // Swedish
|
||||
{ "sw", { "j&a&n", "f&e&b", "m&a&r", "a&p&r", "m&e&i", "j&u&n", "j&u&l", "a&g&o", "s&e&p", "o&k&t", "n&o&v", "d&e&s" } }, // Swahili
|
||||
{ "tr", { "o&c&a", "s&u&b", "m&a&r", "n&i&s", "m&a&y", "h&a&z", "t&e&m", "a&g&u", "e&y&l", "e&k&i", "k&a&s", "a&r&a" } } // Turkish
|
||||
};
|
||||
|
||||
#define MONTHS_MAPPINGS_COUNT (sizeof(months_mappings)/sizeof(months_mappings[0]))
|
||||
|
||||
inline const char* const* getMonthsOfYear(const char* lang) {
|
||||
for (size_t i = 0; i < MONTHS_MAPPINGS_COUNT; i++) {
|
||||
if (strcmp(lang, months_mappings[i].lang) == 0)
|
||||
return months_mappings[i].months;
|
||||
}
|
||||
// fallback to English if not found
|
||||
return months_mappings[4].months; // "en" is index 4
|
||||
}
|
||||
|
||||
#endif // MONTHS_LOOKUP_H
|
||||
@@ -13,9 +13,10 @@
|
||||
#include <time.h>
|
||||
#include <WiFiClientSecure.h>
|
||||
|
||||
#include "mfactoryfont.h" // Custom font
|
||||
#include "tz_lookup.h" // Timezone lookup, do not duplicate mapping here!
|
||||
#include "days_lookup.h" // Languages for the Days of the Week
|
||||
#include "mfactoryfont.h" // Custom font
|
||||
#include "tz_lookup.h" // Timezone lookup, do not duplicate mapping here!
|
||||
#include "days_lookup.h" // Languages for the Days of the Week
|
||||
#include "months_lookup.h" // Languages for the Months of the Year
|
||||
|
||||
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
|
||||
#define MAX_DEVICES 4
|
||||
@@ -49,6 +50,7 @@ int brightness = 7;
|
||||
bool flipDisplay = false;
|
||||
bool twelveHourToggle = false;
|
||||
bool showDayOfWeek = true;
|
||||
bool showDate = false;
|
||||
bool showHumidity = false;
|
||||
bool colonBlinkEnabled = true;
|
||||
char ntpServer1[64] = "pool.ntp.org";
|
||||
@@ -168,6 +170,7 @@ void loadConfig() {
|
||||
doc[F("flipDisplay")] = flipDisplay;
|
||||
doc[F("twelveHourToggle")] = twelveHourToggle;
|
||||
doc[F("showDayOfWeek")] = showDayOfWeek;
|
||||
doc[F("showDate")] = false;
|
||||
doc[F("showHumidity")] = showHumidity;
|
||||
doc[F("colonBlinkEnabled")] = colonBlinkEnabled;
|
||||
doc[F("ntpServer1")] = ntpServer1;
|
||||
@@ -233,8 +236,10 @@ void loadConfig() {
|
||||
flipDisplay = doc["flipDisplay"] | false;
|
||||
twelveHourToggle = doc["twelveHourToggle"] | false;
|
||||
showDayOfWeek = doc["showDayOfWeek"] | true;
|
||||
showDate = doc["showDate"] | false;
|
||||
showHumidity = doc["showHumidity"] | false;
|
||||
colonBlinkEnabled = doc.containsKey("colonBlinkEnabled") ? doc["colonBlinkEnabled"].as<bool>() : true;
|
||||
showWeatherDescription = doc["showWeatherDescription"] | false;
|
||||
|
||||
String de = doc["dimmingEnabled"].as<String>();
|
||||
dimmingEnabled = (de == "true" || de == "on" || de == "1");
|
||||
@@ -253,10 +258,6 @@ void loadConfig() {
|
||||
else
|
||||
tempSymbol = '[';
|
||||
|
||||
if (doc.containsKey("showWeatherDescription"))
|
||||
showWeatherDescription = doc["showWeatherDescription"];
|
||||
else
|
||||
showWeatherDescription = false;
|
||||
|
||||
// --- COUNTDOWN CONFIG LOADING ---
|
||||
if (doc.containsKey("countdown")) {
|
||||
@@ -412,13 +413,33 @@ void setupTime() {
|
||||
if (!isAPMode) {
|
||||
Serial.println(F("[TIME] Starting NTP sync"));
|
||||
}
|
||||
configTime(0, 0, ntpServer1, ntpServer2);
|
||||
setenv("TZ", ianaToPosix(timeZone), 1);
|
||||
tzset();
|
||||
ntpState = NTP_SYNCING;
|
||||
ntpStartTime = millis();
|
||||
ntpRetryCount = 0;
|
||||
ntpSyncSuccessful = false;
|
||||
|
||||
bool serverOk = false;
|
||||
IPAddress resolvedIP;
|
||||
|
||||
// Try first server if it's not empty
|
||||
if (strlen(ntpServer1) > 0 && WiFi.hostByName(ntpServer1, resolvedIP) == 1) {
|
||||
serverOk = true;
|
||||
}
|
||||
// Try second server if first failed
|
||||
else if (strlen(ntpServer2) > 0 && WiFi.hostByName(ntpServer2, resolvedIP) == 1) {
|
||||
serverOk = true;
|
||||
}
|
||||
|
||||
if (serverOk) {
|
||||
configTime(0, 0, ntpServer1, ntpServer2); // safe to call now
|
||||
setenv("TZ", ianaToPosix(timeZone), 1);
|
||||
tzset();
|
||||
ntpState = NTP_SYNCING;
|
||||
ntpStartTime = millis();
|
||||
ntpRetryCount = 0;
|
||||
ntpSyncSuccessful = false;
|
||||
} else {
|
||||
Serial.println(F("[TIME] NTP server lookup failed — skipping sync"));
|
||||
ntpSyncSuccessful = false;
|
||||
ntpState = NTP_IDLE; // or custom NTP_ERROR state
|
||||
// Trigger your error display here if desired
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -454,6 +475,8 @@ void printConfigToSerial() {
|
||||
Serial.println(twelveHourToggle ? "Yes" : "No");
|
||||
Serial.print(F("Show Day of the Week: "));
|
||||
Serial.println(showDayOfWeek ? "Yes" : "No");
|
||||
Serial.print(F("Show Date: "));
|
||||
Serial.println(showDate ? "Yes" : "No");
|
||||
Serial.print(F("Show Weather Description: "));
|
||||
Serial.println(showWeatherDescription ? "Yes" : "No");
|
||||
Serial.print(F("Show Humidity: "));
|
||||
@@ -550,6 +573,7 @@ void setupWebServer() {
|
||||
else if (n == "flipDisplay") doc[n] = (v == "true" || v == "on" || v == "1");
|
||||
else if (n == "twelveHourToggle") doc[n] = (v == "true" || v == "on" || v == "1");
|
||||
else if (n == "showDayOfWeek") doc[n] = (v == "true" || v == "on" || v == "1");
|
||||
else if (n == "showDate") doc[n] = (v == "true" || v == "on" || v == "1");
|
||||
else if (n == "showHumidity") doc[n] = (v == "true" || v == "on" || v == "1");
|
||||
else if (n == "colonBlinkEnabled") doc[n] = (v == "true" || v == "on" || v == "1");
|
||||
else if (n == "dimStartHour") doc[n] = v.toInt();
|
||||
@@ -779,6 +803,17 @@ void setupWebServer() {
|
||||
request->send(200, "application/json", "{\"ok\":true}");
|
||||
});
|
||||
|
||||
server.on("/set_showdate", HTTP_POST, [](AsyncWebServerRequest *request) {
|
||||
bool showDateVal = false;
|
||||
if (request->hasParam("value", true)) {
|
||||
String v = request->getParam("value", true)->value();
|
||||
showDateVal = (v == "1" || v == "true" || v == "on");
|
||||
}
|
||||
showDate = showDateVal;
|
||||
Serial.printf("[WEBSERVER] Set showDate to %d\n", showDate);
|
||||
request->send(200, "application/json", "{\"ok\":true}");
|
||||
});
|
||||
|
||||
server.on("/set_humidity", HTTP_POST, [](AsyncWebServerRequest *request) {
|
||||
bool showHumidityNow = false;
|
||||
if (request->hasParam("value", true)) {
|
||||
@@ -1188,7 +1223,8 @@ DisplayMode key:
|
||||
0: Clock
|
||||
1: Weather
|
||||
2: Weather Description
|
||||
3: Countdown (NEW)
|
||||
3: Countdown
|
||||
4: Nightscout
|
||||
*/
|
||||
|
||||
void setup() {
|
||||
@@ -1241,13 +1277,77 @@ void setup() {
|
||||
|
||||
|
||||
|
||||
// void advanceDisplayMode() {
|
||||
// int oldMode = displayMode;
|
||||
// String ntpField = String(ntpServer2);
|
||||
// bool nightscoutConfigured = ntpField.startsWith("https://");
|
||||
|
||||
// if (displayMode == 0) { // Clock -> ...
|
||||
// if (weatherAvailable && (strlen(openWeatherApiKey) == 32) && (strlen(openWeatherCity) > 0) && (strlen(openWeatherCountry) > 0)) {
|
||||
// displayMode = 1;
|
||||
// Serial.println(F("[DISPLAY] Switching to display mode: WEATHER (from Clock)"));
|
||||
// } else if (countdownEnabled && !countdownFinished && ntpSyncSuccessful) {
|
||||
// displayMode = 3;
|
||||
// Serial.println(F("[DISPLAY] Switching to display mode: COUNTDOWN (from Clock, weather skipped)"));
|
||||
// } else if (nightscoutConfigured) {
|
||||
// displayMode = 4; // Clock -> Nightscout (if weather & countdown are skipped)
|
||||
// Serial.println(F("[DISPLAY] Switching to display mode: NIGHTSCOUT (from Clock, weather & countdown skipped)"));
|
||||
// } else {
|
||||
// displayMode = 0;
|
||||
// Serial.println(F("[DISPLAY] Staying in CLOCK (from Clock)"));
|
||||
// }
|
||||
// } else if (displayMode == 1) { // Weather -> ...
|
||||
// if (showWeatherDescription && weatherAvailable && weatherDescription.length() > 0) {
|
||||
// displayMode = 2;
|
||||
// Serial.println(F("[DISPLAY] Switching to display mode: DESCRIPTION (from Weather)"));
|
||||
// } else if (countdownEnabled && !countdownFinished && ntpSyncSuccessful) {
|
||||
// displayMode = 3;
|
||||
// Serial.println(F("[DISPLAY] Switching to display mode: COUNTDOWN (from Weather)"));
|
||||
// } else if (nightscoutConfigured) {
|
||||
// displayMode = 4; // Weather -> Nightscout (if description & countdown are skipped)
|
||||
// Serial.println(F("[DISPLAY] Switching to display mode: NIGHTSCOUT (from Weather, description & countdown skipped)"));
|
||||
// } else {
|
||||
// displayMode = 0;
|
||||
// Serial.println(F("[DISPLAY] Switching to display mode: CLOCK (from Weather)"));
|
||||
// }
|
||||
// } else if (displayMode == 2) { // Weather Description -> ...
|
||||
// if (countdownEnabled && !countdownFinished && ntpSyncSuccessful) {
|
||||
// displayMode = 3;
|
||||
// Serial.println(F("[DISPLAY] Switching to display mode: COUNTDOWN (from Description)"));
|
||||
// } else if (nightscoutConfigured) {
|
||||
// displayMode = 4; // Description -> Nightscout (if countdown is skipped)
|
||||
// Serial.println(F("[DISPLAY] Switching to display mode: NIGHTSCOUT (from Description, countdown skipped)"));
|
||||
// } else {
|
||||
// displayMode = 0;
|
||||
// Serial.println(F("[DISPLAY] Switching to display mode: CLOCK (from Description)"));
|
||||
// }
|
||||
// } else if (displayMode == 3) { // Countdown -> Nightscout
|
||||
// if (nightscoutConfigured) {
|
||||
// displayMode = 4;
|
||||
// Serial.println(F("[DISPLAY] Switching to display mode: NIGHTSCOUT (from Countdown)"));
|
||||
// } else {
|
||||
// displayMode = 0;
|
||||
// Serial.println(F("[DISPLAY] Switching to display mode: CLOCK (from Countdown)"));
|
||||
// }
|
||||
// } else if (displayMode == 4) { // Nightscout -> Clock
|
||||
// displayMode = 0;
|
||||
// Serial.println(F("[DISPLAY] Switching to display mode: CLOCK (from Nightscout)"));
|
||||
// }
|
||||
|
||||
// // --- Common cleanup/reset logic remains the same ---
|
||||
// lastSwitch = millis();
|
||||
// }
|
||||
|
||||
void advanceDisplayMode() {
|
||||
int oldMode = displayMode;
|
||||
String ntpField = String(ntpServer2);
|
||||
bool nightscoutConfigured = ntpField.startsWith("https://");
|
||||
|
||||
if (displayMode == 0) { // Clock -> ...
|
||||
if (weatherAvailable && (strlen(openWeatherApiKey) == 32) && (strlen(openWeatherCity) > 0) && (strlen(openWeatherCountry) > 0)) {
|
||||
if (showDate) {
|
||||
displayMode = 5; // Date mode right after Clock
|
||||
Serial.println(F("[DISPLAY] Switching to display mode: DATE (from Clock)"));
|
||||
} else if (weatherAvailable && (strlen(openWeatherApiKey) == 32) && (strlen(openWeatherCity) > 0) && (strlen(openWeatherCountry) > 0)) {
|
||||
displayMode = 1;
|
||||
Serial.println(F("[DISPLAY] Switching to display mode: WEATHER (from Clock)"));
|
||||
} else if (countdownEnabled && !countdownFinished && ntpSyncSuccessful) {
|
||||
@@ -1260,6 +1360,20 @@ void advanceDisplayMode() {
|
||||
displayMode = 0;
|
||||
Serial.println(F("[DISPLAY] Staying in CLOCK (from Clock)"));
|
||||
}
|
||||
} else if (displayMode == 5) { // Date mode
|
||||
if (weatherAvailable && (strlen(openWeatherApiKey) == 32) && (strlen(openWeatherCity) > 0) && (strlen(openWeatherCountry) > 0)) {
|
||||
displayMode = 1;
|
||||
Serial.println(F("[DISPLAY] Switching to display mode: WEATHER (from Date)"));
|
||||
} else if (countdownEnabled && !countdownFinished && ntpSyncSuccessful) {
|
||||
displayMode = 3;
|
||||
Serial.println(F("[DISPLAY] Switching to display mode: COUNTDOWN (from Date, weather skipped)"));
|
||||
} else if (nightscoutConfigured) {
|
||||
displayMode = 4;
|
||||
Serial.println(F("[DISPLAY] Switching to display mode: NIGHTSCOUT (from Date, weather & countdown skipped)"));
|
||||
} else {
|
||||
displayMode = 0;
|
||||
Serial.println(F("[DISPLAY] Switching to display mode: CLOCK (from Date)"));
|
||||
}
|
||||
} else if (displayMode == 1) { // Weather -> ...
|
||||
if (showWeatherDescription && weatherAvailable && weatherDescription.length() > 0) {
|
||||
displayMode = 2;
|
||||
@@ -2036,5 +2150,92 @@ void loop() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//DATE Display Mode
|
||||
else if (displayMode == 5 && showDate) {
|
||||
|
||||
// --- VALID DATE CHECK ---
|
||||
if (timeinfo.tm_year < 120 || timeinfo.tm_mday <= 0 || timeinfo.tm_mon < 0 || timeinfo.tm_mon > 11) {
|
||||
advanceDisplayMode();
|
||||
return; // skip drawing
|
||||
}
|
||||
// -------------------------
|
||||
String dateString;
|
||||
|
||||
// Get localized month names
|
||||
const char *const *months = getMonthsOfYear(language);
|
||||
String monthAbbr = String(months[timeinfo.tm_mon]).substring(0, 5);
|
||||
monthAbbr.toLowerCase();
|
||||
|
||||
// Add spaces between day digits
|
||||
String dayString = String(timeinfo.tm_mday);
|
||||
String spacedDay = "";
|
||||
for (size_t i = 0; i < dayString.length(); i++) {
|
||||
spacedDay += dayString[i];
|
||||
if (i < dayString.length() - 1) spacedDay += " ";
|
||||
}
|
||||
|
||||
// Function to check if day should come first for given language
|
||||
auto isDayFirst = [](const String &lang) {
|
||||
// Languages with DD-MM order
|
||||
const char *dayFirstLangs[] = {
|
||||
"af", // Afrikaans
|
||||
"cs", // Czech
|
||||
"da", // Danish
|
||||
"de", // German
|
||||
"eo", // Esperanto
|
||||
"es", // Spanish
|
||||
"et", // Estonian
|
||||
"fi", // Finnish
|
||||
"fr", // French
|
||||
"hr", // Croatian
|
||||
"hu", // Hungarian
|
||||
"it", // Italian
|
||||
"lt", // Lithuanian
|
||||
"lv", // Latvian
|
||||
"nl", // Dutch
|
||||
"no", // Norwegian
|
||||
"pl", // Polish
|
||||
"pt", // Portuguese
|
||||
"ro", // Romanian
|
||||
"sk", // Slovak
|
||||
"sl", // Slovenian
|
||||
"sr", // Serbian
|
||||
"sv", // Swedish
|
||||
"sw", // Swahili
|
||||
"tr" // Turkish
|
||||
};
|
||||
for (auto lf : dayFirstLangs) {
|
||||
if (lang.equalsIgnoreCase(lf)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
String langForDate = String(language);
|
||||
|
||||
if (langForDate == "ja") {
|
||||
// Japanese: month number (spaced digits) + day + symbol
|
||||
String spacedMonth = "";
|
||||
String monthNum = String(timeinfo.tm_mon + 1);
|
||||
dateString = monthAbbr + " " + spacedDay + " ±";
|
||||
|
||||
} else {
|
||||
if (isDayFirst(language)) {
|
||||
dateString = spacedDay + " " + monthAbbr;
|
||||
} else {
|
||||
dateString = monthAbbr + " " + spacedDay;
|
||||
}
|
||||
}
|
||||
|
||||
P.setTextAlignment(PA_CENTER);
|
||||
P.setCharSpacing(0);
|
||||
P.print(dateString);
|
||||
|
||||
if (millis() - lastSwitch > weatherDuration) {
|
||||
advanceDisplayMode();
|
||||
}
|
||||
}
|
||||
yield();
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
"ntpServer2": "time.nist.gov",
|
||||
"twelveHourToggle": false,
|
||||
"showDayOfWeek": true,
|
||||
"showDate": false,
|
||||
"showHumidity": false,
|
||||
"colonBlinkEnabled": true,
|
||||
"language": "en",
|
||||
|
||||
@@ -322,7 +322,7 @@ textarea::placeholder {
|
||||
<button type="button" class="primary-button" id="geo-button" onclick="getLocation()" style="margin-top: 1rem;">Get My Location</button>
|
||||
|
||||
<div class="small">
|
||||
<strong>Location format examples:</strong> City, Country Code - Osaka, JP | ZIP,Country Code - 94040, US | Latitude, Longitude - 34.6937, 135.5023
|
||||
<strong>Location format examples:</strong> City, Country Code - Osaka, JP | ZIP, Country Code - 94040, US | Latitude, Longitude - 34.6937, 135.5023
|
||||
</div>
|
||||
<div class="geo-note" style="display: none;">
|
||||
<br>
|
||||
@@ -495,6 +495,14 @@ textarea::placeholder {
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label style="display: flex; align-items: center; margin-top: 1.75rem; justify-content: space-between;">
|
||||
<span style="margin-right: 0.5em;">Show Date:</span>
|
||||
<span class="toggle-switch">
|
||||
<input type="checkbox" id="showDate" name="showDate" onchange="setShowDate(this.checked)">
|
||||
<span class="toggle-slider"></span>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label style="display: flex; align-items: center; margin-top: 1.75rem; justify-content: space-between;">
|
||||
<span style="margin-right: 0.5em;">Display 12-hour Clock:</span>
|
||||
<span class="toggle-switch">
|
||||
@@ -655,6 +663,7 @@ window.onload = function () {
|
||||
document.getElementById('ntpServer2').value = data.ntpServer2 || "";
|
||||
document.getElementById('twelveHourToggle').checked = !!data.twelveHourToggle;
|
||||
document.getElementById('showDayOfWeek').checked = !!data.showDayOfWeek;
|
||||
document.getElementById('showDate').checked = !!data.showDate;
|
||||
document.getElementById('showHumidity').checked = !!data.showHumidity;
|
||||
document.getElementById('colonBlinkEnabled').checked = !!data.colonBlinkEnabled;
|
||||
document.getElementById('showWeatherDescription').checked = !!data.showWeatherDescription;
|
||||
@@ -796,6 +805,7 @@ async function submitConfig(event) {
|
||||
formData.set('flipDisplay', document.getElementById('flipDisplay').checked ? 'on' : '');
|
||||
formData.set('twelveHourToggle', document.getElementById('twelveHourToggle').checked ? 'on' : '');
|
||||
formData.set('showDayOfWeek', document.getElementById('showDayOfWeek').checked ? 'on' : '');
|
||||
formData.set('showDate', document.getElementById('showDate').checked ? 'on' : '');
|
||||
formData.set('showHumidity', document.getElementById('showHumidity').checked ? 'on' : '');
|
||||
formData.set('colonBlinkEnabled', document.getElementById('colonBlinkEnabled').checked ? 'on' : '');
|
||||
|
||||
@@ -1098,6 +1108,14 @@ function setShowDayOfWeek(val) {
|
||||
});
|
||||
}
|
||||
|
||||
function setShowDate(val) {
|
||||
fetch('/set_showdate', {
|
||||
method: 'POST',
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: "value=" + (val ? 1 : 0)
|
||||
});
|
||||
}
|
||||
|
||||
function setColonBlink(val) {
|
||||
fetch('/set_colon_blink', {
|
||||
method: 'POST',
|
||||
|
||||
50
ESPTimeCast_ESP8266/months_lookup.h
Normal file
50
ESPTimeCast_ESP8266/months_lookup.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#ifndef MONTHS_LOOKUP_H
|
||||
#define MONTHS_LOOKUP_H
|
||||
|
||||
typedef struct {
|
||||
const char* lang;
|
||||
const char* months[12]; // Jan to Dec
|
||||
} MonthsMapping;
|
||||
|
||||
const MonthsMapping months_mappings[] = {
|
||||
{ "af", { "j&a&n", "f&e&b", "m&a&r", "a&p&r", "m&e&i", "j&u&n", "j&u&l", "a&u&g", "s&e&p", "o&k&t", "n&o&v", "d&e&s" } }, // Afrikaans
|
||||
{ "cs", { "l&e&d", "u&n&o", "b&r&e", "d&u&b", "k&v&e", "c&e&r", "c&v&c", "s&r&p", "z&a&r", "r&i&j", "l&i&s", "p&r&o" } }, // Czech
|
||||
{ "da", { "j&a&n", "f&e&b", "m&a&r", "a&p&r", "m&a&j", "j&u&n", "j&u&l", "a&u&g", "s&e&p", "o&k&t", "n&o&v", "d&e&c" } }, // Danish
|
||||
{ "de", { "j&a&n", "f&e&b", "m&a&r", "a&p&r", "m&a&i", "j&u&n", "j&u&l", "a&u&g", "s&e&p", "o&k&t", "n&o&v", "d&e&z" } }, // German
|
||||
{ "en", { "j&a&n", "f&e&b", "m&a&r", "a&p&r", "m&a&y", "j&u&n", "j&u&l", "a&u&g", "s&e&p", "o&c&t", "n&o&v", "d&e&c" } }, // English
|
||||
{ "eo", { "j&a&n", "f&e&b", "m&a&r", "a&p&r", "m&a&j", "j&u&n", "j&u&l", "a&u&g", "s&e&p", "o&k&t", "n&o&v", "d&e&c" } }, // Esperanto
|
||||
{ "es", { "e&n&e", "f&e&b", "m&a&r", "a&b&r", "m&a&y", "j&u&n", "j&u&l", "a&g&o", "s&e&p", "o&c&t", "n&o&v", "d&i&c" } }, // Spanish
|
||||
{ "et", { "j&a&n", "v&e&b", "m&a&r", "a&p&r", "m&a&i", "j&u&n", "j&u&l", "a&u&g", "s&e&p", "o&k&t", "n&o&v", "d&e&t" } }, // Estonian
|
||||
{ "fi", { "t&a&m", "h&e&l", "m&a&a", "h&u&h", "t&o&u", "k&e&s", "h&e&i", "e&l&o", "s&y&y", "l&o&k", "m&a&r", "j&o&u" } }, // Finnish
|
||||
{ "fr", { "j&a&n", "f&e&v", "m&a&r", "a&v&r", "m&a&i", "j&u&n", "j&u&l", "a&o&u", "s&e&p", "o&c&t", "n&o&v", "d&e&c" } }, // French
|
||||
{ "hr", { "s&i&j", "v&e&l", "o&z&u", "t&r&a", "s&v&i", "l&i&p", "s&r&p", "k&o&l", "r&u&j", "l&i&s", "s&t&u", "p&r&o" } }, // Croatian
|
||||
{ "hu", { "j&a&n", "f&e&b", "m&a&r", "a&p&r", "m&a&j", "j&u&n", "j&u&l", "a&u&g", "s&z&e", "o&k&t", "n&o&v", "d&e&c" } }, // Hungarian
|
||||
{ "it", { "g&e&n", "f&e&b", "m&a&r", "a&p&r", "m&a&g", "g&i&u", "l&u&g", "a&g&o", "s&e&t", "o&t&t", "n&o&v", "d&i&c" } }, // Italian
|
||||
{ "ja", { "1 ²", "2 ²", "3 ²", "4 ²", "5 ²", "6 ²", "7 ²", "8 ²", "9 ²", "10 ²", "11 ²", "12 ²" } }, // Japanese
|
||||
{ "lt", { "s&a&u", "v&a&s", "k&o&v", "b&a&l", "g&e&g", "b&i&r", "l&i&e", "r&u&g", "s&w&e", "s&p&a", "l&a&p", "g&r&u" } }, // Lithuanian
|
||||
{ "lv", { "j&a&n", "f&e&b", "m&a&r", "a&p&r", "m&a&i", "j&u&n", "j&u&l", "a&u&g", "s&e&p", "o&k&t", "n&o&v", "d&e&c" } }, // Latvian
|
||||
{ "nl", { "j&a&n", "f&e&b", "m&a&a", "a&p&r", "m&e&i", "j&u&n", "j&u&l", "a&u&g", "s&e&p", "o&k&t", "n&o&v", "d&e&c" } }, // Dutch
|
||||
{ "no", { "j&a&n", "f&e&b", "m&a&r", "a&p&r", "m&a&i", "j&u&n", "j&u&l", "a&u&g", "s&e&p", "o&k&t", "n&o&v", "d&e&s" } }, // Norwegian
|
||||
{ "pl", { "s&t&y", "l&u&t", "m&a&r", "k&w&i", "m&a&j", "c&z&e", "l&i&p", "s&i&e", "w&r&z", "p&a&z", "l&i&s", "g&r&u" } }, // Polish
|
||||
{ "pt", { "j&a&n", "f&e&v", "m&a&r", "a&b&r", "m&a&i", "j&u&n", "j&u&l", "a&g&o", "s&e&t", "o&u&t", "n&o&v", "d&e&z" } }, // Portuguese
|
||||
{ "ro", { "i&a&n", "f&e&b", "m&a&r", "a&p&r", "m&a&i", "i&u&n", "i&u&l", "a&u&g", "s&e&p", "o&c&t", "n&o&v", "d&e&c" } }, // Romanian
|
||||
{ "sk", { "j&a&n", "f&e&b", "m&a&r", "a&p&r", "m&a&j", "j&u&n", "j&u&l", "a&u&g", "s&e&p", "o&k&t", "n&o&v", "d&e&c" } }, // Slovak
|
||||
{ "sl", { "j&a&n", "f&e&b", "m&a&r", "a&p&r", "m&a&j", "j&u&n", "j&u&l", "a&v&g", "s&e&p", "o&k&t", "n&o&v", "d&e&c" } }, // Slovenian
|
||||
{ "sr", { "j&a&n", "f&e&b", "m&a&r", "a&p&r", "m&a&j", "j&u&n", "j&u&l", "a&v&g", "s&e&p", "o&k&t", "n&o&v", "d&e&c" } }, // Serbian
|
||||
{ "sv", { "j&a&n", "f&e&b", "m&a&r", "a&p&r", "m&a&j", "j&u&n", "j&u&l", "a&u&g", "s&e&p", "o&k&t", "n&o&v", "d&e&c" } }, // Swedish
|
||||
{ "sw", { "j&a&n", "f&e&b", "m&a&r", "a&p&r", "m&e&i", "j&u&n", "j&u&l", "a&g&o", "s&e&p", "o&k&t", "n&o&v", "d&e&s" } }, // Swahili
|
||||
{ "tr", { "o&c&a", "s&u&b", "m&a&r", "n&i&s", "m&a&y", "h&a&z", "t&e&m", "a&g&u", "e&y&l", "e&k&i", "k&a&s", "a&r&a" } } // Turkish
|
||||
};
|
||||
|
||||
#define MONTHS_MAPPINGS_COUNT (sizeof(months_mappings)/sizeof(months_mappings[0]))
|
||||
|
||||
inline const char* const* getMonthsOfYear(const char* lang) {
|
||||
for (size_t i = 0; i < MONTHS_MAPPINGS_COUNT; i++) {
|
||||
if (strcmp(lang, months_mappings[i].lang) == 0)
|
||||
return months_mappings[i].months;
|
||||
}
|
||||
// fallback to English if not found
|
||||
return months_mappings[4].months; // "en" is index 4
|
||||
}
|
||||
|
||||
#endif // MONTHS_LOOKUP_H
|
||||
Reference in New Issue
Block a user