mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-02-01 19:30:31 +00:00
d7acb84e56
git-svn-id: https://svn.fhem.de/fhem/trunk@21159 2b470e98-0d58-463d-a4d8-8e2adae1ed80
2172 lines
58 KiB
Perl
2172 lines
58 KiB
Perl
###############################################################################
|
|
# $Id$
|
|
package main;
|
|
|
|
# only to suppress file reload error in FHEM
|
|
sub UConv_Initialize() { }
|
|
|
|
package UConv;
|
|
use strict;
|
|
use warnings;
|
|
use POSIX;
|
|
use utf8;
|
|
|
|
use Math::Trig;
|
|
use Math::Trig ':pi';
|
|
use Math::Trig ':radial';
|
|
use Math::Trig ':great_circle';
|
|
use Scalar::Util qw(looks_like_number);
|
|
use Time::HiRes qw(gettimeofday);
|
|
use Time::Local;
|
|
use Encode;
|
|
|
|
#use Data::Dumper;
|
|
|
|
sub _ReplaceStringByHashKey($$;$);
|
|
|
|
####################
|
|
# Translations
|
|
|
|
our %compasspoints = (
|
|
en => [
|
|
[ "North", "N", '▲' ],
|
|
[ "North-Northeast", "NNE", '⬈' ],
|
|
[ "North-East", "NE", '⬈' ],
|
|
[ "East-Northeast", "ENE", '⬈' ],
|
|
[ "East", "E", '▶' ],
|
|
[ "East-Southeast", "ESE", '⬊' ],
|
|
[ "Southeast", "SE", '⬊' ],
|
|
[ "South-Southeast", "SSE", '⬊' ],
|
|
[ "South", "S", '▼' ],
|
|
[ "South-Southwest", "SSW", '⬋' ],
|
|
[ "Southwest", "SW", '⬋' ],
|
|
[ "West-Southwest", "WSW", '⬋' ],
|
|
[ "West", "W", '◀' ],
|
|
[ "West-Northwest", "WNW", '⬉' ],
|
|
[ "Northwest", "NW", '⬉' ],
|
|
[ "North-Northwest", "NNW", '⬉' ],
|
|
],
|
|
de => [
|
|
[ "Norden", "N", '▲' ],
|
|
[ "Nord-Nordost", "NNO", '⬈' ],
|
|
[ "Nordost", "NO", '⬈' ],
|
|
[ "Ost-Nordost", "ONO", '⬈' ],
|
|
[ "Osten", "O", '▶' ],
|
|
[ "Ost-Südost", "OSO", '⬊' ],
|
|
[ "Südost", "SO", '⬊' ],
|
|
[ "Süd-Südost", "SSO", '⬊' ],
|
|
[ "Süden", "S", '▼' ],
|
|
[ "Süd-Südwest", "SSW", '⬋' ],
|
|
[ "Südwest", "SW", '⬋' ],
|
|
[ "West-Südwest", "WSW", '⬋' ],
|
|
[ "Westen", "W", '◀' ],
|
|
[ "West-Nordwest", "WNW", '⬉' ],
|
|
[ "Nordwest", "NW", '⬉' ],
|
|
[ "Nord-Nordwest", "NNW", '⬉' ],
|
|
],
|
|
es => [
|
|
[ "Norte", "N", '▲' ],
|
|
[ "Norte-Noreste", "NNE", '⬈' ],
|
|
[ "Noreste", "NE", '⬈' ],
|
|
[ "Este-Noreste", "ENE", '⬈' ],
|
|
[ "Este", "E", '▶' ],
|
|
[ "Este-Sureste", "ESE", '⬊' ],
|
|
[ "Sureste", "SE", '⬊' ],
|
|
[ "Sur-Sureste", "SSE", '⬊' ],
|
|
[ "Sur", "S", '▼' ],
|
|
[ "Sudoeste", "SDO", '⬋' ],
|
|
[ "Sur-Oeste", "SO", '⬋' ],
|
|
[ "Oeste-Suroeste", "OSO", '⬋' ],
|
|
[ "Oeste", "O", '◀' ],
|
|
[ "Oeste-Noroeste", "ONO", '⬉' ],
|
|
[ "Noroeste", "NO", '⬉' ],
|
|
[ "Norte-Noroeste", "NNE", '⬉' ],
|
|
],
|
|
it => [
|
|
[ "Nord", "N", '▲' ],
|
|
[ "Nord-Nord-Est", "NNE", '⬈' ],
|
|
[ "Nord-Est", "NE", '⬈' ],
|
|
[ "Est-Nord-Est", "ENE", '⬈' ],
|
|
[ "Est", "E", '▶' ],
|
|
[ "Est-Sud-Est", "ESE", '⬊' ],
|
|
[ "Sud-Est", "SE", '⬊' ],
|
|
[ "Sud-Sud-Est", "SSE", '⬊' ],
|
|
[ "Sud", "S", '▼' ],
|
|
[ "Sud-Sud-Ovest", "SSO", '⬋' ],
|
|
[ "Sud-Ovest", "SO", '⬋' ],
|
|
[ "Ovest-Sud-Ovest", "OSO", '⬋' ],
|
|
[ "Ovest", "O", '◀' ],
|
|
[ "Ovest-Nord-Ovest", "ONO", '⬉' ],
|
|
[ "Nord-Ovest", "NO", '⬉' ],
|
|
[ "Nord-Nord-Ovest", "NNO", '⬉' ],
|
|
],
|
|
nl => [
|
|
[ "Noorden", "N", '▲' ],
|
|
[ "Noord-Noordoosten", "NNO", '⬈' ],
|
|
[ "Noordoosten", "NO", '⬈' ],
|
|
[ "Oost-Noordoost", "ONO", '⬈' ],
|
|
[ "Oosten", "O", '▶' ],
|
|
[ "Oost-Zuidoost", "OZO", '⬊' ],
|
|
[ "Zuidoosten", "ZO", '⬊' ],
|
|
[ "Zuid-Zuidoost", "ZZO", '⬊' ],
|
|
[ "Zuiden", "Z", '▼' ],
|
|
[ "Zuid-Zuidwest", "ZZW", '⬋' ],
|
|
[ "Zuidwest", "ZW", '⬋' ],
|
|
[ "West-Zuidwest", "WZW", '⬋' ],
|
|
[ "West", "W", '◀' ],
|
|
[ "West-Noord-West", "WNW", '⬉' ],
|
|
[ "Noord-West", "NW", '⬉' ],
|
|
[ "Noord-Noord-West", "NNW", '⬉' ],
|
|
],
|
|
fr => [
|
|
[ "Nord", "N", '▲' ],
|
|
[ "Nord-Nord-Est", "NNE", '⬈' ],
|
|
[ "Nord-Est", "NE", '⬈' ],
|
|
[ "Est-Nord-Est", "ENE", '⬈' ],
|
|
[ "Est", "E", '▶' ],
|
|
[ "Est-Sud-Est", "ESE", '⬊' ],
|
|
[ "Sud-Est", "SE", '⬊' ],
|
|
[ "Sud-Sud-Est", "SSE", '⬊' ],
|
|
[ "Sud", "S", '▼' ],
|
|
[ "Sud-Sud-Ouest", "SSW", '⬋' ],
|
|
[ "Sud-Ouest", "SW", '⬋' ],
|
|
[ "Ouest-Sud-Ouest", "OSO", '⬋' ],
|
|
[ "Ouest", "O", '◀' ],
|
|
[ "Ouest-Nord-Ouest", "ONO", '⬉' ],
|
|
[ "Nord-Ouest", "NO", '⬉' ],
|
|
[ "Nord-Nord-Ouest", "NNO", '⬉' ],
|
|
],
|
|
pl => [
|
|
[ "Północ", "N", '▲' ],
|
|
[ "Północny-Północny-Wschód", "NNE", '⬈' ],
|
|
[ "Północny-Wschód", "NE", '⬈' ],
|
|
[ "Wschód-Północny-Wschód", "ENE", '⬈' ],
|
|
[ "Wschód", "E", '▶' ],
|
|
[ "Wschód-Południowy-Wschód", "ESE", '⬊' ],
|
|
[ "Południowy-Południowy-Wschód", "SE", '⬊' ],
|
|
[ "Południowy-Wschód", "SSE", '⬊' ],
|
|
[ "Południe", "S", '▼' ],
|
|
[ "Południowo-Południowy-Zachód", "SSW", '⬋' ],
|
|
[ "Południowy-Zachód", "SW", '⬋' ],
|
|
[ "Zachód-Południowy-Zachód", "WSW", '⬋' ],
|
|
[ "Zachód", "W", '◀' ],
|
|
[ "Zachód-Północny-Zachód", "WNW", '⬉' ],
|
|
[ "Północny-Zachód", "NW", '⬉' ],
|
|
[ "Północno-Północny-Zachód", "NNW", '⬉' ],
|
|
],
|
|
);
|
|
|
|
our %hr_formats = (
|
|
|
|
# 1 234 567.89
|
|
std => {
|
|
delim => "\x{2009}",
|
|
sep => ".",
|
|
},
|
|
|
|
# 1 234 567,89
|
|
'std-fr' => {
|
|
delim => "\x{2009}",
|
|
sep => ",",
|
|
},
|
|
|
|
# 1,234,567.89
|
|
'old-english' => {
|
|
delim => ",",
|
|
sep => ".",
|
|
},
|
|
|
|
# 1.234.567,89
|
|
'old-european' => {
|
|
delim => ".",
|
|
sep => ",",
|
|
},
|
|
|
|
# 1'234'567.89
|
|
ch => {
|
|
delim => "'",
|
|
sep => ".",
|
|
},
|
|
|
|
### lang ref ###
|
|
#
|
|
|
|
en => { ref => "std", },
|
|
|
|
de => { ref => "std-fr", },
|
|
|
|
de_at => {
|
|
ref => "std-fr",
|
|
min => 4,
|
|
},
|
|
|
|
de_ch => { ref => "std", },
|
|
|
|
nl => { ref => "std-fr", },
|
|
|
|
fr => { ref => "std-fr", },
|
|
|
|
pl => { ref => "std-fr", },
|
|
|
|
### number ref ###
|
|
#
|
|
|
|
0 => { ref => "std", },
|
|
1 => { ref => "std-fr", },
|
|
2 => { ref => "old-english", },
|
|
3 => { ref => "old-european", },
|
|
4 => { ref => "ch", },
|
|
5 => {
|
|
ref => "std-fr",
|
|
min => 4,
|
|
},
|
|
|
|
);
|
|
|
|
our %daytimes = (
|
|
en => [
|
|
"morning", "midmorning", "noon", "afternoon",
|
|
"evening", "midevening", "night",
|
|
],
|
|
de => [
|
|
"Morgen", "Vormittag", "Mittag", "Nachmittag",
|
|
"Vorabend", "Abend", "Nacht",
|
|
],
|
|
nl => [
|
|
"Ochtend", "Vormiddag", "Middag", "Nachmiddag",
|
|
"Avond", "Midavond", "Nacht",
|
|
],
|
|
fr => [
|
|
"Matin", "Martinée", "Midi", "Après-midi", "Veille", "Soir", "Nuit",
|
|
],
|
|
pl => [
|
|
"Ranek", "Rano", "Południe", "Popołudnie",
|
|
"Wigilia", "Wieczór", "Noc",
|
|
],
|
|
icons => [
|
|
"weather_sunrise", "scene_day",
|
|
"weather_sun", "weather_summer",
|
|
"weather_sunset", "scene_night",
|
|
"weather_moon_phases_8",
|
|
],
|
|
);
|
|
|
|
our %sdt2daytimes = (
|
|
|
|
# User overwrite format:
|
|
# <SeasonSrc><SeasonIndex><DST><daytimeStage>:<daytime>
|
|
# M000:0
|
|
# M001:0
|
|
# M002:0
|
|
# M003:1
|
|
# M004:1
|
|
# M005:2
|
|
# M006:2
|
|
# M007:3
|
|
# M008:3
|
|
# M009:3
|
|
# M0010:3
|
|
# M0011:4
|
|
# M0012:5
|
|
#
|
|
# M010:0
|
|
# M011:0
|
|
# M012:0
|
|
# M013:1
|
|
# M014:1
|
|
# M015:2
|
|
# M016:2
|
|
# M017:3
|
|
# M018:3
|
|
# M019:3
|
|
# M0110:3
|
|
# M0111:4
|
|
# M0112:5
|
|
|
|
# SPRING SEASON
|
|
0 => {
|
|
|
|
# DST = no
|
|
0 => {
|
|
1 => 0,
|
|
4 => 1,
|
|
6 => 2,
|
|
8 => 3,
|
|
12 => 4,
|
|
},
|
|
|
|
# DST = yes
|
|
1 => {
|
|
1 => 0,
|
|
4 => 1,
|
|
6 => 2,
|
|
8 => 3,
|
|
12 => 4,
|
|
},
|
|
},
|
|
|
|
# SUMMER SEASON
|
|
1 => {
|
|
|
|
# DST = yes
|
|
1 => {
|
|
1 => 0,
|
|
4 => 1,
|
|
6 => 2,
|
|
7 => 3,
|
|
10 => 4,
|
|
12 => 5,
|
|
}
|
|
},
|
|
|
|
# FALL SEASON
|
|
2 => {
|
|
|
|
# DST = no
|
|
0 => {
|
|
1 => 0,
|
|
4 => 1,
|
|
6 => 2,
|
|
7 => 3,
|
|
11 => 4,
|
|
},
|
|
|
|
# DST = yes
|
|
1 => {
|
|
1 => 0,
|
|
4 => 1,
|
|
6 => 2,
|
|
7 => 3,
|
|
11 => 4,
|
|
},
|
|
},
|
|
|
|
# WINTER SEASON
|
|
3 => {
|
|
|
|
# DST = no
|
|
0 => {
|
|
1 => 0,
|
|
3 => 1,
|
|
6 => 2,
|
|
8 => 3,
|
|
12 => 4,
|
|
},
|
|
},
|
|
);
|
|
|
|
our %seasons = (
|
|
en => [ "Spring", "Summer", "Fall", "Winter", ],
|
|
de => [ "Frühling", "Sommer", "Herbst", "Winter", ],
|
|
nl => [ "Voorjaar", "Zomer", "Herfst", "Winter", ],
|
|
fr => [ "Printemps", "Été", "Automne", "Hiver", ],
|
|
pl => [ "Wiosna", "Lato", "Jesień", "Zima", ],
|
|
pheno => [ 2, 4, 7, 9 ],
|
|
);
|
|
|
|
our %seasonsPheno = (
|
|
en => [
|
|
"Early Spring",
|
|
"First Spring",
|
|
"Full Spring",
|
|
"Early Summer",
|
|
"Midsummer",
|
|
"Late Summer",
|
|
"Early Fall",
|
|
"Fall",
|
|
"Late Fall",
|
|
"Winter",
|
|
],
|
|
de => [
|
|
"Vorfrühling", "Erstfrühling", "Vollfrühling", "Frühsommer",
|
|
"Hochsommer", "Spätsommer", "Frühherbst", "Vollherbst",
|
|
"Spätherbst", "Winter",
|
|
],
|
|
nl => [
|
|
"Vroeg Voorjaar",
|
|
"Eerste Voorjaar",
|
|
"Voorjaar",
|
|
"Vroeg Zomer",
|
|
"Zomer",
|
|
"Laat Zomer",
|
|
"Vroeg Herfst",
|
|
"Herfst",
|
|
"Laat Herfst",
|
|
"Winter",
|
|
],
|
|
fr => [
|
|
"Avant du printemps",
|
|
"Début du printemps",
|
|
"Printemps",
|
|
"Avant de l'été",
|
|
"Milieu de l'été",
|
|
"Fin de l'été",
|
|
"Avant de l'automne",
|
|
"Automne",
|
|
"Fin de l'automne",
|
|
"Hiver",
|
|
],
|
|
pl => [
|
|
"Przedwiośnie",
|
|
"Pierwsza Wiosna",
|
|
"Wiosna",
|
|
"Wczesne Lato",
|
|
"Połowa Lata",
|
|
"Późnym Latem",
|
|
"Wczesną Jesienią",
|
|
"Jesień",
|
|
"Późną Jesienią",
|
|
"Zima",
|
|
],
|
|
);
|
|
|
|
our %dst = (
|
|
en => [ "standard", "daylight" ],
|
|
de => [ "Normalzeit", "Sommerzeit" ],
|
|
nl => [ "Standaardtijd", "Zomertijd" ],
|
|
fr => [ "Heure normale", "L'heure d'été" ],
|
|
pl => [ "Standardowy czas", "Czas Letni" ],
|
|
);
|
|
|
|
our %daystages = (
|
|
en => [ "weekday", "weekend", "holiday", "vacation", ],
|
|
de => [ "Wochentag", "Wochenende", "Feiertag", "Urlaubstag", ],
|
|
nl => [ "Weekdag", "Weekend", "Vieringsdag", "Vakantiedag", ],
|
|
fr => [ "Jour de la semaine", "Weekend", "Vacances", "Villégiature", ],
|
|
pl => [ "dzień powszedni", "Weekend", "święto", "Wakacjach", ],
|
|
);
|
|
|
|
our %reldays = (
|
|
en => [ "yesterday", "today", "tomorrow" ],
|
|
de => [ "gestern", "heute", "morgen" ],
|
|
nl => [ "gisteren", "vandaag", "morgen" ],
|
|
fr => [ "hier", "aujourd'hui", "demain" ],
|
|
pl => [ "wczoraj", "dzisiaj", "jutro" ],
|
|
);
|
|
|
|
our %monthss = (
|
|
en => [
|
|
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug",
|
|
"Sep", "Oct", "Nov", "Dec", "Jan"
|
|
],
|
|
de => [
|
|
"Jan", "Feb", "Mar", "Apr", "Mai", "Jun", "Jul", "Aug",
|
|
"Sep", "Okt", "Nov", "Dez", "Jan"
|
|
],
|
|
nl => [
|
|
"Jan", "Feb", "Mar", "Apr", "Mei", "Jun", "Jul", "Aug",
|
|
"Sep", "Okt", "Nov", "Dec", "Jan"
|
|
],
|
|
fr => [
|
|
"Jan", "Fév", "Mar", "Avr", "Mai", "Jun", "Jul", "Aût",
|
|
"Sep", "Oct", "Nov", "Dec", "Jan"
|
|
],
|
|
pl => [
|
|
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug",
|
|
"Sep", "Oct", "Nov", "Dec", "Jan"
|
|
],
|
|
);
|
|
|
|
our %months = (
|
|
en => [
|
|
"January", "Febuary", "March", "April",
|
|
"May", "June", "July", "August",
|
|
"September", "October", "November", "December",
|
|
"January"
|
|
],
|
|
de => [
|
|
"Januar", "Februar", "März", "April",
|
|
"Mai", "Juni", "Juli", "August",
|
|
"September", "Oktober", "November", "Dezember",
|
|
"Januar"
|
|
],
|
|
nl => [
|
|
"Januari", "Februari", "Maart", "April",
|
|
"Mei", "Juni", "Juli", "Augustus",
|
|
"September", "Oktober", "November", "December",
|
|
"Januari"
|
|
],
|
|
fr => [
|
|
"Janvier", "Février", "Mars", "Avril",
|
|
"Mai", "Juin", "Juillet", "Août",
|
|
"Septembre", "Octobre", "Novembre", "Décembre",
|
|
"Janvier"
|
|
],
|
|
pl => [
|
|
"Styczeń", "Luty", "Marzec", "Kwiecień",
|
|
"Maj", "Czerwiec", "July", "Lipiec",
|
|
"Wrzesień", "Październik", "Listopad", "Grudzień",
|
|
"Styczeń"
|
|
],
|
|
);
|
|
|
|
our %dayss = (
|
|
en => [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" ],
|
|
de => [ "So", "Mo", "Di", "Mi", "Do", "Fr", "Sa", "So" ],
|
|
nl => [ "Zon", "Maa", "Din", "Woe", "Don", "Vri", "Zat", "Zon" ],
|
|
fr => [ "Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim" ],
|
|
pl => [ "Nie", "Pon", "Wto", "śro", "Czw", "Pią", "Sob", "Nie" ],
|
|
);
|
|
|
|
our %days = (
|
|
en => [
|
|
"Sunday", "Monday", "Tuesday", "Wednesday",
|
|
"Thursday", "Friday", "Saturday", "Sunday"
|
|
],
|
|
de => [
|
|
"Sonntag", "Montag", "Dienstag", "Mittwoch",
|
|
"Donnerstag", "Freitag", "Samstag", "Sonntag"
|
|
],
|
|
nl => [
|
|
"Zondag", "Maandag", "Dinsdag", "Woensdag",
|
|
"Donderdag", "Vrijdag", "Zaterdag", "Zondag"
|
|
],
|
|
fr => [
|
|
"Dimanche", "Lundi", "Mardi", "Mercredi",
|
|
"Jeudi", "Vendredi", "Samedi", "Dimanche"
|
|
],
|
|
pl => [
|
|
"Niedziela", "Poniedziałek", "Wtorek", "środa",
|
|
"Czwartek", "Piątek", "Sobota", "Niedziela"
|
|
],
|
|
);
|
|
|
|
our %dateformats = (
|
|
en => '%wday_long%, %mon_long% %mday%',
|
|
de => '%wday_long%, %mday%. %mon_long%',
|
|
nl => '%wday_long%, %mday%. %mon_long%',
|
|
fr => '%wday_long%, %mday%. %mon_long%',
|
|
pl => '%wday_long%, %mday%. %mon_long%',
|
|
);
|
|
|
|
our %dateformatss = (
|
|
en => '%mon_long% %mday%',
|
|
de => '%mday%. %mon_long%',
|
|
nl => '%mday%. %mon_long%',
|
|
fr => '%mday%. %mon_long%',
|
|
pl => '%mday%. %mon_long%',
|
|
);
|
|
|
|
# https://www.luftfeuchtigkeit-raumklima.de/tabelle.php
|
|
our %ideal_clima = (
|
|
bathroom => {
|
|
c => [ -273.15, 6, 16, 20, 23, 27, ],
|
|
h => [ 0, 40, 50, 70, 80 ],
|
|
},
|
|
living => {
|
|
c => [ -273.15, 6, 16, 20, 23, 27, ],
|
|
h => [ 0, 30, 40, 60, 70 ],
|
|
},
|
|
kitchen => {
|
|
c => [ -273.15, 6, 16, 18, 20, 27, ],
|
|
h => [ 0, 40, 50, 60, 70 ],
|
|
},
|
|
bedroom => {
|
|
c => [ -273.15, 6, 12, 17, 20, 23, ],
|
|
h => [ 0, 30, 40, 60, 70 ],
|
|
},
|
|
hallway => {
|
|
c => [ -273.15, 6, 12, 15, 18, 23, ],
|
|
h => [ 0, 30, 40, 60, 70 ],
|
|
},
|
|
cellar => {
|
|
c => [ -273.15, 6, 7, 10, 15, 20, ],
|
|
h => [ 0, 40, 50, 60, 70 ],
|
|
},
|
|
outdoor => {
|
|
c => [ -273.15, 2.5, 5, 14, 30, 35, ],
|
|
h => [ 0, 40, 50, 70, 80 ],
|
|
},
|
|
);
|
|
|
|
our %clima_names = (
|
|
c => {
|
|
en => [ "freeze", "cold", "low", "ideal", "high", "hot" ],
|
|
de => [ "frostig", "kalt", "niedrig", "optimal", "hoch", "heiß" ],
|
|
nl => [ "kil", "koude", "laag", "optimale", "hoog", "heet" ],
|
|
fr => [ "froid", "froid", "faible", "optimal", "haut", "chaud" ],
|
|
pl =>
|
|
[ "chłodny", "zimno", "niski", "optymalny", "wysoki", "gorący" ],
|
|
rgb => [ "0055BB", "0066CC", "009999", "4C9329", "E7652B", "C72A23" ],
|
|
},
|
|
h => {
|
|
en => [ "dry", "low", "ideal", "high", "wet" ],
|
|
de => [ "trocken", "niedrig", "optimal", "hoch", "nass" ],
|
|
nl => [ "droog", "laag", "optimale", "hoog", "nat" ],
|
|
fr => [ "sec", "faible", "optimal", "haut", "humide" ],
|
|
pl => [ "suchy", "niski", "optymalny", "wysoki", "mokro" ],
|
|
rgb => [ "C72A23", "E7652B", "4C9329", "009999", "0066CC" ],
|
|
}
|
|
);
|
|
|
|
#################################
|
|
### Inner metric conversions
|
|
###
|
|
|
|
# Temperature: convert Celsius to Kelvin
|
|
sub c2k($;$) {
|
|
my ( $data, $rnd ) = @_;
|
|
return _round( $data + 273.15, $rnd );
|
|
}
|
|
|
|
# Temperature: convert Kelvin to Celsius
|
|
sub k2c($;$) {
|
|
my ( $data, $rnd ) = @_;
|
|
return _round( $data - 273.15, $rnd );
|
|
}
|
|
|
|
# Speed: convert km/h (kilometer per hour) to m/s (meter per second)
|
|
sub kph2mps($;$) {
|
|
my ( $data, $rnd ) = @_;
|
|
return _round( $data / 3.6, $rnd );
|
|
}
|
|
|
|
# Speed: convert m/s (meter per second) to km/h (kilometer per hour)
|
|
sub mps2kph($;$) {
|
|
my ( $data, $rnd ) = @_;
|
|
return _round( $data * 3.6, $rnd );
|
|
}
|
|
|
|
# Pressure: convert hPa (hecto Pascal) to mmHg (milimeter of Mercury)
|
|
sub hpa2mmhg($;$) {
|
|
my ( $data, $rnd ) = @_;
|
|
return _round( $data * 0.00750061561303, $rnd );
|
|
}
|
|
|
|
#################################
|
|
### Metric to angloamerican conversions
|
|
###
|
|
|
|
# Temperature: convert Celsius to Fahrenheit
|
|
sub c2f($;$) {
|
|
my ( $data, $rnd ) = @_;
|
|
return _round( $data * 1.8 + 32, $rnd );
|
|
}
|
|
|
|
# Temperature: convert Kelvin to Fahrenheit
|
|
sub k2f($;$) {
|
|
my ( $data, $rnd ) = @_;
|
|
return _round( ( $data - 273.15 ) * 1.8 + 32, $rnd );
|
|
}
|
|
|
|
# Pressure: convert hPa (hecto Pascal) to in (inches of Mercury)
|
|
sub hpa2inhg($;$) {
|
|
my ( $data, $rnd ) = @_;
|
|
return _round( $data * 0.02952998751, $rnd );
|
|
}
|
|
|
|
# Pressure: convert hPa (hecto Pascal) to PSI (Pound force per square inch)
|
|
sub hpa2psi($;$) {
|
|
my ( $data, $rnd ) = @_;
|
|
return _round( $data * 100.00014504, $rnd );
|
|
}
|
|
|
|
# Speed: convert km/h (kilometer per hour) to mph (miles per hour)
|
|
sub kph2mph($;$) {
|
|
return km2mi(@_);
|
|
}
|
|
|
|
# Speed: convert m/s (meter per seconds) to mph (miles per hour)
|
|
sub mps2mph($;$) {
|
|
my ( $data, $rnd ) = @_;
|
|
return _round( kph2mph( mps2kph( $data, 9 ), 9 ), $rnd );
|
|
}
|
|
|
|
# Length: convert mm (milimeter) to in (inch)
|
|
sub mm2in($;$) {
|
|
my ( $data, $rnd ) = @_;
|
|
return _round( $data * 0.039370, $rnd );
|
|
}
|
|
|
|
# Length: convert cm (centimeter) to in (inch)
|
|
sub cm2in($;$) {
|
|
my ( $data, $rnd ) = @_;
|
|
return _round( $data * 0.39370, $rnd );
|
|
}
|
|
|
|
# Length: convert m (meter) to ft (feet)
|
|
sub m2ft($;$) {
|
|
my ( $data, $rnd ) = @_;
|
|
return _round( $data * 3.2808, $rnd );
|
|
}
|
|
|
|
# Length: convert km (kilometer) to miles (mi)
|
|
sub km2mi($;$) {
|
|
my ( $data, $rnd ) = @_;
|
|
return _round( $data * 0.621371192, $rnd );
|
|
}
|
|
|
|
#################################
|
|
### Inner Angloamerican conversions
|
|
###
|
|
|
|
# Speed: convert mph (miles per hour) to ft/s (feet per second)
|
|
sub mph2fts($;$) {
|
|
my ( $data, $rnd ) = @_;
|
|
return _round( $data * 1.467, $rnd );
|
|
}
|
|
|
|
# Speed: convert ft/s (feet per second) to mph (miles per hour)
|
|
sub fts2mph($;$) {
|
|
my ( $data, $rnd ) = @_;
|
|
return _round( $data / 1.467, $rnd );
|
|
}
|
|
|
|
#################################
|
|
### Angloamerican to Metric conversions
|
|
###
|
|
|
|
# Temperature: convert Fahrenheit to Celsius
|
|
sub f2c($;$) {
|
|
my ( $data, $rnd ) = @_;
|
|
return _round( ( $data - 32 ) * 0.5556, $rnd );
|
|
}
|
|
|
|
# Temperature: convert Fahrenheit to Kelvin
|
|
sub f2k($;$) {
|
|
my ( $data, $rnd ) = @_;
|
|
return _round( ( $data - 32 ) / 1.8 + 273.15, $rnd );
|
|
}
|
|
|
|
# Pressure: convert in (inches of Mercury) to hPa (hecto Pascal)
|
|
sub inhg2hpa($;$) {
|
|
my ( $data, $rnd ) = @_;
|
|
return _round( $data * 33.8638816, $rnd );
|
|
}
|
|
|
|
# Pressure: convert PSI (Pound force per square inch) to hPa (hecto Pascal)
|
|
sub psi2hpa($;$) {
|
|
my ( $data, $rnd ) = @_;
|
|
return _round( $data / 100.00014504, $rnd );
|
|
}
|
|
|
|
# Speed: convert mph (miles per hour) to km/h (kilometer per hour)
|
|
sub mph2kph($;$) {
|
|
return mi2km(@_);
|
|
}
|
|
|
|
# Speed: convert mph (miles per hour) to m/s (meter per seconds)
|
|
sub mph2mps($;$) {
|
|
my ( $data, $rnd ) = @_;
|
|
return _round( kph2mps( mph2kph( $data, 9 ), 9 ), $rnd );
|
|
}
|
|
|
|
# Length: convert in (inch) to mm (milimeter)
|
|
sub in2mm($;$) {
|
|
my ( $data, $rnd ) = @_;
|
|
return _round( $data * 25.4, $rnd );
|
|
}
|
|
|
|
# Length: convert in (inch) to cm (centimeter)
|
|
sub in2cm($;$) {
|
|
my ( $data, $rnd ) = @_;
|
|
return _round( $data / 0.39370, $rnd );
|
|
}
|
|
|
|
# Length: convert ft (feet) to m (meter)
|
|
sub ft2m($;$) {
|
|
my ( $data, $rnd ) = @_;
|
|
return _round( $data / 3.2808, $rnd );
|
|
}
|
|
|
|
# Length: convert mi (miles) to km (kilometer)
|
|
sub mi2km($;$) {
|
|
my ( $data, $rnd ) = @_;
|
|
return _round( $data * 1.609344, $rnd );
|
|
}
|
|
|
|
#################################
|
|
### Plane angle conversions
|
|
###
|
|
|
|
# convert direction in degree to point of the compass
|
|
sub direction2compasspoint($;$$) {
|
|
my ( $deg, $txt, $lang ) = @_;
|
|
my $i = floor( ( ( $deg + 11.25 ) % 360 ) / 22.5 );
|
|
return $i if ( !wantarray && defined($txt) && $txt == "0" );
|
|
|
|
my $directions_txt_i18n;
|
|
$lang = main::AttrVal( "global", "language", "EN" ) unless ($lang);
|
|
|
|
if ( exists( $compasspoints{ lc($lang) } ) ) {
|
|
$directions_txt_i18n = $compasspoints{ lc($lang) };
|
|
}
|
|
else {
|
|
$directions_txt_i18n = $compasspoints{en};
|
|
}
|
|
|
|
return (
|
|
encode_utf8 $directions_txt_i18n->[$i][0],
|
|
$directions_txt_i18n->[$i][1],
|
|
encode_utf8 $directions_txt_i18n->[$i][2]
|
|
) if wantarray;
|
|
return encode_utf8 $directions_txt_i18n->[$i][2] if ( $txt && $txt eq "3" );
|
|
return encode_utf8 $directions_txt_i18n->[$i][0] if ( $txt && $txt eq "2" );
|
|
return $directions_txt_i18n->[$i][1];
|
|
}
|
|
|
|
# see below for compasspoint2compasspoint() text translations.
|
|
|
|
#################################
|
|
### Solar conversions
|
|
###
|
|
|
|
# Power: convert uW/cm2 (micro watt per square centimeter) to UV-Index
|
|
sub uwpscm2uvi($;$) {
|
|
my ( $data, $rnd ) = @_;
|
|
|
|
return 0 unless ($data);
|
|
|
|
# Forum topic,44403.msg501704.html#msg501704
|
|
return int( ( $data - 100 ) / 450 + 1 ) unless ( defined($rnd) );
|
|
|
|
$rnd = 0 unless ( defined($rnd) );
|
|
return _round( ( ( $data - 100 ) / 450 + 1 ), $rnd );
|
|
}
|
|
|
|
# Power: convert UV-Index to uW/cm2 (micro watt per square centimeter)
|
|
sub uvi2uwpscm($) {
|
|
my ($data) = @_;
|
|
|
|
return 0 unless ($data);
|
|
return ( $data * ( 450 + 1 ) ) + 100;
|
|
}
|
|
|
|
# Power: convert lux to W/m2 (watt per square meter)
|
|
sub lux2wpsm($;$) {
|
|
my ( $data, $rnd ) = @_;
|
|
|
|
# Forum topic,44403.msg501704.html#msg501704
|
|
return _round( $data / 126.7, $rnd );
|
|
}
|
|
|
|
# Power: convert W/m2 to lux
|
|
sub wpsm2lux($;$) {
|
|
my ( $data, $rnd ) = @_;
|
|
|
|
# Forum topic,44403.msg501704.html#msg501704
|
|
return _round( $data * 126.7, $rnd );
|
|
}
|
|
|
|
#################################
|
|
### Nautic unit conversions
|
|
###
|
|
|
|
# Speed: convert smi (statute miles) to nmi (nautical miles)
|
|
sub smi2nmi($;$) {
|
|
my ( $data, $rnd ) = @_;
|
|
return _round( $data * 0.8684, $rnd );
|
|
}
|
|
|
|
# Speed: convert km (kilometer) to nmi (nautical miles)
|
|
sub km2nmi($;$) {
|
|
my ( $data, $rnd ) = @_;
|
|
return _round( smi2nmi( km2mi( $data, 9 ), 9 ), $rnd );
|
|
}
|
|
|
|
# Speed: convert km/h to knots
|
|
sub kph2kn($;$) {
|
|
my ( $data, $rnd ) = @_;
|
|
return _round( $data * 0.539956803456, $rnd );
|
|
}
|
|
|
|
# Speed: convert km/h to Beaufort wind force scale
|
|
sub kph2bft($) {
|
|
my ($data) = @_;
|
|
my $val = "0";
|
|
|
|
if ( $data >= 118 ) {
|
|
$val = "12";
|
|
}
|
|
elsif ( $data >= 103 ) {
|
|
$val = "11";
|
|
}
|
|
elsif ( $data >= 89 ) {
|
|
$val = "10";
|
|
}
|
|
elsif ( $data >= 75 ) {
|
|
$val = "9";
|
|
}
|
|
elsif ( $data >= 62 ) {
|
|
$val = "8";
|
|
}
|
|
elsif ( $data >= 50 ) {
|
|
$val = "7";
|
|
}
|
|
elsif ( $data >= 39 ) {
|
|
$val = "6";
|
|
}
|
|
elsif ( $data >= 29 ) {
|
|
$val = "5";
|
|
}
|
|
elsif ( $data >= 20 ) {
|
|
$val = "4";
|
|
}
|
|
elsif ( $data >= 12 ) {
|
|
$val = "3";
|
|
}
|
|
elsif ( $data >= 6 ) {
|
|
$val = "2";
|
|
}
|
|
elsif ( $data >= 1 ) {
|
|
$val = "1";
|
|
}
|
|
|
|
if (wantarray) {
|
|
my ( $cond, $rgb, $warn ) = bft2condition($val);
|
|
return ( $val, $rgb, $cond, $warn );
|
|
}
|
|
return $val;
|
|
}
|
|
|
|
# Speed: convert mph (miles per hour) to Beaufort wind force scale
|
|
sub mph2bft($) {
|
|
my ($data) = @_;
|
|
my $val = "0";
|
|
|
|
if ( $data >= 73 ) {
|
|
$val = "12";
|
|
}
|
|
elsif ( $data >= 64 ) {
|
|
$val = "11";
|
|
}
|
|
elsif ( $data >= 55 ) {
|
|
$val = "10";
|
|
}
|
|
elsif ( $data >= 47 ) {
|
|
$val = "9";
|
|
}
|
|
elsif ( $data >= 39 ) {
|
|
$val = "8";
|
|
}
|
|
elsif ( $data >= 32 ) {
|
|
$val = "7";
|
|
}
|
|
elsif ( $data >= 25 ) {
|
|
$val = "6";
|
|
}
|
|
elsif ( $data >= 19 ) {
|
|
$val = "5";
|
|
}
|
|
elsif ( $data >= 13 ) {
|
|
$val = "4";
|
|
}
|
|
elsif ( $data >= 8 ) {
|
|
$val = "3";
|
|
}
|
|
elsif ( $data >= 4 ) {
|
|
$val = "2";
|
|
}
|
|
elsif ( $data >= 1 ) {
|
|
$val = "1";
|
|
}
|
|
|
|
if (wantarray) {
|
|
my ( $cond, $rgb, $warn ) = bft2condition($val);
|
|
return ( $val, $rgb, $cond, $warn );
|
|
}
|
|
return $val;
|
|
}
|
|
|
|
#################################
|
|
### Differential conversions
|
|
###
|
|
|
|
sub distance($$$$;$$) {
|
|
my ( $lat1, $lng1, $lat2, $lng2, $rnd, $unit ) = @_;
|
|
return _round( "0.000000000", $rnd )
|
|
if ( $lat1 eq $lat2 && $lng1 eq $lng2 );
|
|
|
|
my $aearth = 6378.137; # GRS80/WGS84 semi major axis of earth ellipsoid
|
|
my $km = great_circle_distance(
|
|
deg2rad($lat1), pi / 2. - deg2rad($lng1),
|
|
deg2rad($lat2), pi / 2. - deg2rad($lng2),
|
|
$aearth
|
|
);
|
|
|
|
return _round(
|
|
(
|
|
$unit && $unit eq "nmi" ? km2nmi($km) : ( $unit ? km2mi($km) : $km )
|
|
),
|
|
$rnd
|
|
);
|
|
}
|
|
|
|
sub duration ($$;$) {
|
|
my ( $datetimeNow, $datetimeOld, $format ) = @_;
|
|
|
|
if ( $datetimeNow eq "" || $datetimeOld eq "" ) {
|
|
$datetimeNow = "1970-01-01 00:00:00";
|
|
$datetimeOld = "1970-01-01 00:00:00";
|
|
}
|
|
|
|
my $timestampNow = main::time_str2num($datetimeNow);
|
|
my $timestampOld = main::time_str2num($datetimeOld);
|
|
my $timeDiff = $timestampNow - $timestampOld;
|
|
|
|
# return seconds
|
|
return _round( $timeDiff, 0 )
|
|
if ( defined($format) && $format eq "sec" );
|
|
|
|
# return minutes
|
|
return _round( $timeDiff / 60, 0 )
|
|
if ( defined($format) && $format eq "min" );
|
|
|
|
# return human readable format
|
|
return s2hms( _round( $timeDiff, 0 ) );
|
|
}
|
|
|
|
#################################
|
|
### Textual unit conversions
|
|
###
|
|
|
|
# Translate point of the compass to other languages
|
|
# Also works to convert from short text to long text and vice versa.
|
|
sub compasspoint2compasspoint($;$$$) {
|
|
my ( $shortTxt, $fromLang, $txt, $toLang ) = @_;
|
|
return $shortTxt if ( !$shortTxt || $shortTxt eq "" );
|
|
return direction2compasspoint( $shortTxt, $txt,
|
|
$toLang ? $toLang : $fromLang )
|
|
if ( $shortTxt =~ m/^\d+(?:\.\d+)?$/ );
|
|
return compasspoint2direction( $shortTxt, $fromLang )
|
|
if ( defined($txt) && $txt eq "0" );
|
|
|
|
my $fromDirections_txt_i18n;
|
|
$fromLang = "EN" unless ($fromLang);
|
|
if ( exists( $compasspoints{ lc($fromLang) } ) ) {
|
|
$fromDirections_txt_i18n = $compasspoints{ lc($fromLang) };
|
|
}
|
|
else {
|
|
$fromLang = "EN";
|
|
$fromDirections_txt_i18n = $compasspoints{en};
|
|
}
|
|
|
|
my $toDirections_txt_i18n;
|
|
$toLang = main::AttrVal( "global", "language", "EN" )
|
|
unless ($toLang);
|
|
if ( exists( $compasspoints{ lc($toLang) } ) ) {
|
|
$toDirections_txt_i18n = $compasspoints{ lc($toLang) };
|
|
}
|
|
else {
|
|
$toLang = "EN";
|
|
$toDirections_txt_i18n = $compasspoints{en};
|
|
}
|
|
|
|
my $i;
|
|
my $i2;
|
|
my $f = 0;
|
|
foreach my $a ( @{$fromDirections_txt_i18n} ) {
|
|
my $f2 = 0;
|
|
foreach my $b ( @{$a} ) {
|
|
if ( lc($b) eq lc( decode_utf8($shortTxt) ) ) {
|
|
$i2 = $f2;
|
|
last;
|
|
}
|
|
$f2++;
|
|
}
|
|
if ( defined($i2) ) {
|
|
$i = $f;
|
|
last;
|
|
}
|
|
$f++;
|
|
}
|
|
|
|
unless ( defined($txt) ) {
|
|
$txt = 1;
|
|
$txt = 3 if ( $i2 eq "2" );
|
|
$txt = 2 if ( $i2 eq "0" );
|
|
}
|
|
|
|
unless ( defined($i) ) {
|
|
return ( "", "", "" ) if wantarray;
|
|
return "";
|
|
}
|
|
|
|
return (
|
|
encode_utf8 $toDirections_txt_i18n->[$i][0],
|
|
$toDirections_txt_i18n->[$i][1],
|
|
encode_utf8 $toDirections_txt_i18n->[$i][2]
|
|
) if wantarray;
|
|
return encode_utf8 $toDirections_txt_i18n->[$i][2] if ( $txt eq "3" );
|
|
return encode_utf8 $toDirections_txt_i18n->[$i][0] if ( $txt eq "2" );
|
|
return $toDirections_txt_i18n->[$i][1];
|
|
}
|
|
|
|
# convert point of the compass to direction in degree
|
|
sub compasspoint2direction($;$) {
|
|
my ( $shortTxt, $fromLang ) = @_;
|
|
return $shortTxt if ( !$shortTxt || $shortTxt eq "" );
|
|
|
|
my $fromDirections_txt_i18n;
|
|
$fromLang = "EN" unless ($fromLang);
|
|
if ( exists( $compasspoints{ lc($fromLang) } ) ) {
|
|
$fromDirections_txt_i18n = $compasspoints{ lc($fromLang) };
|
|
}
|
|
else {
|
|
$fromLang = "EN";
|
|
$fromDirections_txt_i18n = $compasspoints{en};
|
|
}
|
|
|
|
my $i;
|
|
my $i2;
|
|
my $f = 0;
|
|
foreach my $a ( @{$fromDirections_txt_i18n} ) {
|
|
my $f2 = 0;
|
|
foreach my $b ( @{$a} ) {
|
|
if ( lc($b) eq lc( decode_utf8($shortTxt) ) ) {
|
|
$i2 = $f2;
|
|
last;
|
|
}
|
|
$f2++;
|
|
}
|
|
if ( defined($i2) ) {
|
|
$i = $f;
|
|
last;
|
|
}
|
|
$f += 22.5;
|
|
}
|
|
|
|
unless ( defined($i) ) {
|
|
return ("") if wantarray;
|
|
return "";
|
|
}
|
|
|
|
return $i;
|
|
}
|
|
|
|
# Convert an arabic number to roman numerals
|
|
sub arabic2roman ($) {
|
|
my ($n) = @_;
|
|
my %items = ();
|
|
my @r;
|
|
return "" if ( !$n || $n eq "" || $n !~ m/^\d+(?:\.\d+)?$/ || $n eq "0" );
|
|
return $n
|
|
if ( $n >= 1000001. ); # numbers above cannot be displayed/converted
|
|
|
|
my %roman = (
|
|
1 => 'I',
|
|
5 => 'V',
|
|
10 => 'X',
|
|
50 => 'L',
|
|
100 => 'C',
|
|
500 => 'D',
|
|
1000 => 'M',
|
|
5000 => '(V)',
|
|
10000 => '(X)',
|
|
50000 => '(L)',
|
|
100000 => '(C)',
|
|
500000 => '(D)',
|
|
1000000 => '(M)',
|
|
);
|
|
|
|
for my $v ( sort { $b <=> $a } keys %roman ) {
|
|
my $c = int( $n / $v );
|
|
next unless ($c);
|
|
$items{ $roman{$v} } = $c;
|
|
$n -= $v * $c;
|
|
}
|
|
|
|
my @th = sort { $a <=> $b } keys %roman;
|
|
|
|
for ( my $i = 0 ; $i < @th ; $i++ ) {
|
|
my $v = $th[$i];
|
|
next if ( $v >= 1000000. ); # numbers above have no greater icon
|
|
my $k = $roman{$v};
|
|
my $c = $items{$k};
|
|
next unless ($c);
|
|
|
|
my $gv = $th[ $i + 1. ];
|
|
my $gk = $roman{$gv};
|
|
|
|
if ( $c == 4 || ( $gv / $v == $c ) ) {
|
|
$items{$gk}++;
|
|
$c = $gv - $c * $v;
|
|
$items{$k} = $c * -1;
|
|
|
|
}
|
|
}
|
|
|
|
for my $v ( sort { $b <=> $a } keys %roman ) {
|
|
my $l = $roman{$v};
|
|
my $c = $items{$l};
|
|
next unless ($c);
|
|
|
|
if ( $c > 0 ) {
|
|
push @r, $l for ( 1 .. $c );
|
|
}
|
|
else {
|
|
push @r, ( $l, pop @r );
|
|
}
|
|
}
|
|
|
|
return join '', @r;
|
|
}
|
|
|
|
######## humanReadable #########################################
|
|
# What : Formats a number or text string to be more readable for humans
|
|
# Syntax: { humanReadable( <value>, [ <format> ] ) }
|
|
# Call : { humanReadable(102345.6789) }
|
|
# { humanReadable(102345.6789, 3) }
|
|
# { humanReadable(102345.6789, "DE") }
|
|
# { humanReadable(102345.6789, "si-fr") }
|
|
# { humanReadable(102345.6789, {
|
|
# group=>3, delim=>".", sep=>"," } ) }
|
|
# { humanReadable("DE44500105175407324931", {
|
|
# group=>4, rev=>0 } ) }
|
|
# Source: https://en.wikipedia.org/wiki/Decimal_mark
|
|
# https://de.wikipedia.org/wiki/Schreibweise_von_Zahlen
|
|
# https://de.wikipedia.org/wiki/Dezimaltrennzeichen
|
|
# https://de.wikipedia.org/wiki/Zifferngruppierung
|
|
sub humanReadable($;$) {
|
|
my ( $v, $f ) = @_;
|
|
my $l =
|
|
$main::attr{global}{humanReadable} ? $main::attr{global}{humanReadable}
|
|
: (
|
|
$main::attr{global}{language} ? $main::attr{global}{language}
|
|
: "EN"
|
|
);
|
|
|
|
my $h =
|
|
!$f || ref($f) || !$hr_formats{$f} ? $f
|
|
: (
|
|
$hr_formats{$f}{ref} ? $hr_formats{ $hr_formats{$f}{ref} }
|
|
: $hr_formats{$f}
|
|
);
|
|
my $min =
|
|
ref($h)
|
|
&& defined( $h->{min} ) ? $h->{min}
|
|
: (
|
|
!ref($f)
|
|
&& $hr_formats{$f}{min} ? $hr_formats{$f}{min}
|
|
: 5
|
|
);
|
|
my $group =
|
|
ref($h)
|
|
&& defined( $h->{group} ) ? $h->{group}
|
|
: (
|
|
!ref($f)
|
|
&& $hr_formats{$f}{group} ? $hr_formats{$f}{group}
|
|
: 3
|
|
);
|
|
my $delim =
|
|
ref($h)
|
|
&& $h->{delim} ? $h->{delim}
|
|
: $hr_formats{
|
|
(
|
|
$l =~ /^de|nl|fr|pl/i ? "std-fr"
|
|
: "std"
|
|
)
|
|
}{delim};
|
|
my $sep =
|
|
ref($h)
|
|
&& $h->{sep} ? $h->{sep}
|
|
: $hr_formats{
|
|
(
|
|
$l =~ /^de|nl|fr|pl/i ? "std-fr"
|
|
: "std"
|
|
)
|
|
}{sep};
|
|
my $reverse = ref($h) && defined( $h->{rev} ) ? $h->{rev} : 1;
|
|
|
|
my @p = split( /\./, $v, 2 );
|
|
|
|
if ( length( $p[0] ) < $min && length( $p[1] ) < $min ) {
|
|
$v =~ s/\./$sep/g;
|
|
return $v;
|
|
}
|
|
|
|
$v =~ s/\./\*/g;
|
|
|
|
# digits after thousands separator
|
|
if ( ( $delim eq "\x{202F}" || $delim eq " " )
|
|
&& length( $p[1] ) >= $min )
|
|
{
|
|
$v =~ s/(\w{$group})(?=\w)(?!\w*\*)/$1$delim/g;
|
|
}
|
|
|
|
# digits before thousands separator
|
|
if ( length( $p[0] ) >= $min ) {
|
|
$v = reverse $v if ($reverse);
|
|
$v =~ s/(\w{$group})(?=\w)(?!\w*\*)/$1$delim/g;
|
|
if ($reverse) {
|
|
$v =~ s/\*/$sep/g;
|
|
return scalar reverse $v;
|
|
}
|
|
}
|
|
|
|
$v =~ s/\*/$sep/g;
|
|
return $v;
|
|
}
|
|
|
|
# ######## machineReadable #########################################
|
|
# # What : find the first matching number in a string and make it
|
|
# # machine readable.
|
|
# # Syntax: { machineReadable( <value>, [ <global>, [ <format> ]] ) }
|
|
# # Call : { machineReadable("102 345,6789") }
|
|
# sub machineReadable($;$) {
|
|
# my ( $v, $g ) = @_;
|
|
#
|
|
# sub mrVal($$) {
|
|
# my ( $n, $n2 ) = @_;
|
|
# $n .= "." . $n2 if ($n2);
|
|
# $n =~ s/[^\d\.]//g;
|
|
# return $n;
|
|
# }
|
|
#
|
|
#
|
|
# foreach ( "std", "std-fr" ) {
|
|
# my $delim = '\\' . $hr_formats{$_}{delim};
|
|
# $delim .= ' ' if ($_ =~ /^std/);
|
|
#
|
|
# if ( $g
|
|
# && $v =~
|
|
# s/((-?)((?:\d+(?:[$delim]\d)*)+)([\.\,])((?:\d+(?:[$delim]\d)*)+)?)/$2.mrVal($3, $5)/eg
|
|
# )
|
|
# {
|
|
# last;
|
|
# }
|
|
# elsif ( $v =~
|
|
# m/^((\-?)((?:\d(?:[$delim]\d)*)+)(?:([\.\,])((?:\d(?:[$delim]\d)*)+))?)/ )
|
|
# {
|
|
# $v = $2 . mrVal( $3, $5 );
|
|
# last;
|
|
# }
|
|
# }
|
|
#
|
|
# return $v;
|
|
# }
|
|
|
|
# Condition: convert temperature (Celsius) to temperature condition
|
|
sub c2condition($;$$) {
|
|
my ( $data, $roomType, $lang ) = @_;
|
|
my $val = "?";
|
|
my $rgb = "FFFFFF";
|
|
$lang = "en" if ( !$lang );
|
|
|
|
my $thresholds;
|
|
|
|
if ($roomType) {
|
|
if ( ref($roomType)
|
|
&& ref($roomType) eq 'ARRAY'
|
|
&& scalar @{$roomType} == 6 )
|
|
{
|
|
$thresholds = $roomType;
|
|
}
|
|
elsif (ref($roomType)
|
|
|| looks_like_number($roomType)
|
|
|| !defined( $ideal_clima{$roomType} ) )
|
|
{
|
|
$thresholds = $ideal_clima{living}{c};
|
|
}
|
|
else {
|
|
$thresholds = $ideal_clima{$roomType}{c};
|
|
}
|
|
}
|
|
else {
|
|
$thresholds = $ideal_clima{outdoor}{c};
|
|
}
|
|
|
|
my $i = 0;
|
|
foreach my $th ( @{$thresholds} ) {
|
|
if ( $data > $th ) {
|
|
$val = $clima_names{c}{ lc($lang) }[$i];
|
|
$rgb = $clima_names{c}{rgb}[$i];
|
|
}
|
|
else {
|
|
last;
|
|
}
|
|
$i++;
|
|
}
|
|
|
|
return ( $val, $rgb ) if (wantarray);
|
|
return $val;
|
|
}
|
|
|
|
# Condition: convert humidity (percent) to humidity condition
|
|
sub humidity2condition($;$$) {
|
|
my ( $data, $roomType, $lang ) = @_;
|
|
my $val = "?";
|
|
my $rgb = "FFFFFF";
|
|
$lang = "en" if ( !$lang );
|
|
|
|
my $thresholds;
|
|
|
|
if ($roomType) {
|
|
if ( ref($roomType)
|
|
&& ref($roomType) eq 'ARRAY'
|
|
&& scalar @{$roomType} == 5 )
|
|
{
|
|
$thresholds = $roomType;
|
|
}
|
|
elsif (ref($roomType)
|
|
|| looks_like_number($roomType)
|
|
|| !defined( $ideal_clima{$roomType} ) )
|
|
{
|
|
$thresholds = $ideal_clima{living}{h};
|
|
}
|
|
else {
|
|
$thresholds = $ideal_clima{$roomType}{h};
|
|
}
|
|
}
|
|
else {
|
|
$thresholds = $ideal_clima{outdoor}{h};
|
|
}
|
|
|
|
my $i = 0;
|
|
foreach my $th ( @{$thresholds} ) {
|
|
if ( $data > $th ) {
|
|
$val = $clima_names{h}{ lc($lang) }[$i];
|
|
$rgb = $clima_names{h}{rgb}[$i];
|
|
}
|
|
else {
|
|
last;
|
|
}
|
|
$i++;
|
|
}
|
|
|
|
return ( $val, $rgb ) if (wantarray);
|
|
return $val;
|
|
}
|
|
|
|
# Condition: convert UV-Index to UV condition
|
|
sub uvi2condition($) {
|
|
my ($data) = @_;
|
|
my $val = "low";
|
|
my $rgb = "4C9329";
|
|
|
|
if ( $data > 11 ) {
|
|
$val = "extreme";
|
|
$rgb = "674BC4";
|
|
}
|
|
elsif ( $data > 8 ) {
|
|
$val = "veryhigh";
|
|
$rgb = "C72A23";
|
|
}
|
|
elsif ( $data > 6 ) {
|
|
$val = "high";
|
|
$rgb = "E7652B";
|
|
}
|
|
elsif ( $data > 3 ) {
|
|
$val = "moderate";
|
|
$rgb = "F4E54C";
|
|
}
|
|
|
|
return ( $val, $rgb ) if (wantarray);
|
|
return $val;
|
|
}
|
|
|
|
# Condition: convert Beaufort to wind condition
|
|
sub bft2condition($) {
|
|
my ($data) = @_;
|
|
my $rgb = "FEFEFE";
|
|
my $cond = "calm";
|
|
my $warn = " ";
|
|
|
|
if ( $data == 12 ) {
|
|
$rgb = "E93323";
|
|
$cond = "hurricane_force";
|
|
$warn = "hurricane_force";
|
|
}
|
|
elsif ( $data == 11 ) {
|
|
$rgb = "EB4826";
|
|
$cond = "violent_storm";
|
|
$warn = "storm_force";
|
|
}
|
|
elsif ( $data == 10 ) {
|
|
$rgb = "E96E2C";
|
|
$cond = "storm";
|
|
$warn = "storm_force";
|
|
}
|
|
elsif ( $data == 9 ) {
|
|
$rgb = "F19E38";
|
|
$cond = "strong_gale";
|
|
$warn = "gale_force";
|
|
}
|
|
elsif ( $data == 8 ) {
|
|
$rgb = "F7CE46";
|
|
$cond = "gale";
|
|
$warn = "gale_force";
|
|
}
|
|
elsif ( $data == 7 ) {
|
|
$rgb = "FFFF54";
|
|
$cond = "near_gale";
|
|
$warn = "high_winds";
|
|
}
|
|
elsif ( $data == 6 ) {
|
|
$rgb = "D6FD51";
|
|
$cond = "strong_breeze";
|
|
$warn = "high_winds";
|
|
}
|
|
elsif ( $data == 5 ) {
|
|
$rgb = "B1FC4F";
|
|
$cond = "fresh_breeze";
|
|
}
|
|
elsif ( $data == 4 ) {
|
|
$rgb = "B1FC7B";
|
|
$cond = "moderate_breeze";
|
|
}
|
|
elsif ( $data == 3 ) {
|
|
$rgb = "B1FCA3";
|
|
$cond = "gentle_breeze";
|
|
}
|
|
elsif ( $data == 2 ) {
|
|
$rgb = "B1FCD0";
|
|
$cond = "light_breeze";
|
|
}
|
|
elsif ( $data == 1 ) {
|
|
$rgb = "D6FEFE";
|
|
$cond = "light_air";
|
|
}
|
|
|
|
return ( $cond, $rgb, $warn ) if (wantarray);
|
|
return $cond;
|
|
}
|
|
|
|
sub values2weathercondition($$$$$) {
|
|
my ( $temp, $hum, $light, $isday, $israining ) = @_;
|
|
my $val = "clear";
|
|
|
|
if ($israining) {
|
|
$val = "rain";
|
|
}
|
|
elsif ( $light > 40000 ) {
|
|
$val = "sunny";
|
|
}
|
|
elsif ($isday) {
|
|
$val = "cloudy";
|
|
}
|
|
|
|
$val = "nt_" . $val unless ($isday);
|
|
return $val;
|
|
}
|
|
|
|
#################################
|
|
### Chronological conversions
|
|
###
|
|
|
|
sub hms2s($) {
|
|
my $in = shift;
|
|
my @a = split( ":", $in );
|
|
return 0 if ( scalar @a < 2 || $in !~ m/^[\d:]*$/ );
|
|
return $a[0] * 3600 + $a[1] * 60 + ( $a[2] ? $a[2] : 0 );
|
|
}
|
|
|
|
sub hms2m($) {
|
|
return hms2s(@_) / 60;
|
|
}
|
|
|
|
sub hms2h($) {
|
|
return hms2m(@_) / 60;
|
|
}
|
|
|
|
sub s2hms($) {
|
|
my ($in) = @_;
|
|
my ( $h, $m, $s );
|
|
$h = int( $in / 3600 );
|
|
$m = int( ( $in - $h * 3600 ) / 60 );
|
|
$s = int( $in - $h * 3600 - $m * 60 );
|
|
return ( $h, $m, $s ) if (wantarray);
|
|
return sprintf( "%02d:%02d:%02d", $h, $m, $s );
|
|
}
|
|
|
|
sub m2hms($) {
|
|
my ($in) = @_;
|
|
my ( $h, $m, $s );
|
|
$h = int( $in / 60 );
|
|
$m = int( $in - $h * 60 );
|
|
$s = int( 60 * ( $in - $h * 60 - $m ) );
|
|
return ( $h, $m, $s ) if (wantarray);
|
|
return sprintf( "%02d:%02d:%02d", $h, $m, $s );
|
|
}
|
|
|
|
sub h2hms($) {
|
|
my ($in) = @_;
|
|
my ( $h, $m, $s );
|
|
$h = int($in);
|
|
$m = int( 60 * ( $in - $h ) );
|
|
$s = int( 3600 * ( $in - $h ) - 60 * $m );
|
|
return ( $h, $m, $s ) if (wantarray);
|
|
return sprintf( "%02d:%02d:%02d", $h, $m, $s );
|
|
}
|
|
|
|
sub DaysOfMonth (;$$) {
|
|
my $y = shift;
|
|
my $m = shift;
|
|
|
|
return undef
|
|
unless (
|
|
!$y
|
|
|| $y =~ /^\d{10}(?:\.\d+)?$/
|
|
|| (
|
|
$y =~ /^[1-2]\d{3}$/
|
|
&& ( !$m
|
|
|| $m =~ /^\d{1,2}$/ )
|
|
)
|
|
);
|
|
|
|
my $t = ( $y =~ /^\d{10}(?:\.\d+)?$/ ? $y : gettimeofday() );
|
|
$y = 1900. + ( localtime($t) )[5] unless ($y);
|
|
$m = 1. + ( localtime($t) )[4] if ( !$y || $y !~ /^[1-2]\d{3}$/ );
|
|
$y = 1900. + ( localtime($t) )[5] if ( $y !~ /^[1-2]\d{3}$/ );
|
|
|
|
if ( $m < 8. ) {
|
|
if ( $m % 2 ) {
|
|
return 31.;
|
|
}
|
|
else {
|
|
return 28. + IsLeapYear($y)
|
|
if ( $m == 2. );
|
|
return 30.;
|
|
}
|
|
}
|
|
elsif ( $m % 2. ) {
|
|
return 30.;
|
|
}
|
|
else {
|
|
return 31.;
|
|
}
|
|
}
|
|
|
|
sub IsLeapYear (;$) {
|
|
|
|
# Either the value 0 or the value 1 is returned.
|
|
# If 0, it is not a leap year. If 1, it is a
|
|
# leap year. (Works for Julian calendar,
|
|
# established in 1582)
|
|
|
|
my $y = shift;
|
|
|
|
return undef
|
|
unless ( !$y || $y =~ /^\d{10}(?:\.\d+)?$/ || $y =~ /^[1-2]\d{3}$/ );
|
|
|
|
my $t = ( $y =~ /^\d{10}(?:\.\d+)?$/ ? $y : gettimeofday() );
|
|
$y = 1900. + ( localtime($t) )[5] unless ($y);
|
|
$y = 1900. + ( localtime($t) )[5] if ( $y !~ /^[1-2]\d{3}$/ );
|
|
|
|
# If $year is not evenly divisible by 4, it is
|
|
# not a leap year; therefore, we return the
|
|
# value 0 and do no further calculations in
|
|
# this subroutine. ("$year % 4" provides the
|
|
# remainder when $year is divided by 4.
|
|
# If there is a remainder then $year is
|
|
# not evenly divisible by 4.)
|
|
|
|
return 0 if $y % 4;
|
|
|
|
# At this point, we know $year is evenly divisible
|
|
# by 4. Therefore, if it is not evenly
|
|
# divisible by 100, it is a leap year --
|
|
# we return the value 1 and do no further
|
|
# calculations in this subroutine.
|
|
|
|
return 1 if $y % 100;
|
|
|
|
# At this point, we know $year is evenly divisible
|
|
# by 4 and also evenly divisible by 100. Therefore,
|
|
# if it is not also evenly divisible by 400, it is
|
|
# not leap year -- we return the value 0 and do no
|
|
# further calculations in this subroutine.
|
|
|
|
return 0 if $y % 400;
|
|
|
|
# Now we know $year is evenly divisible by 4, evenly
|
|
# divisible by 100, and evenly divisible by 400.
|
|
# We return the value 1 because it is a leap year.
|
|
|
|
return 1;
|
|
}
|
|
|
|
# Get current stage of the daytime based on temporal hours
|
|
# https://de.wikipedia.org/wiki/Temporale_Stunden
|
|
sub GetDaytime(;$$$$) {
|
|
my ( $time, $totalTemporalHours, $lang, $params ) = @_;
|
|
$lang = (
|
|
$main::attr{global}{language}
|
|
? $main::attr{global}{language}
|
|
: "EN"
|
|
) unless ($lang);
|
|
|
|
my $ret = ref($time) eq "HASH" ? $time : _time( $time, $lang, 1, $params );
|
|
return undef unless ( ref($ret) eq "HASH" );
|
|
|
|
$ret->{daytimeStages} = $totalTemporalHours
|
|
&& $totalTemporalHours =~ m/^\d+$/ ? $totalTemporalHours : 12;
|
|
|
|
# TODO: consider srParams
|
|
$ret->{sunrise} = main::sunrise_abs_dat( $ret->{time_t} );
|
|
$ret->{sunrise_s} = hms2s( $ret->{sunrise} );
|
|
$ret->{sunrise_t} = $ret->{midnight_t} + $ret->{sunrise_s};
|
|
$ret->{sunset} = main::sunset_abs_dat( $ret->{time_t} );
|
|
$ret->{sunset_s} = hms2s( $ret->{sunset} );
|
|
$ret->{sunset_t} = $ret->{midnight_t} + $ret->{sunset_s};
|
|
$ret->{isday} = $ret->{time_t} >= $ret->{sunrise_t}
|
|
&& $ret->{time_t} < $ret->{sunset_t} ? 1 : 0;
|
|
|
|
$ret->{daytimeRel_s} =
|
|
hms2s("$ret->{hour}:$ret->{min}:$ret->{sec}") - $ret->{sunrise_s};
|
|
$ret->{daytimeRel} = s2hms( $ret->{daytimeRel_s} );
|
|
$ret->{daytimeT_s} = $ret->{sunset_s} - $ret->{sunrise_s};
|
|
$ret->{daytimeT} = s2hms( $ret->{daytimeT_s} );
|
|
$ret->{daytimeStageLn_s} = $ret->{daytimeT_s} / $ret->{daytimeStages};
|
|
$ret->{daytimeStageLn} = s2hms( $ret->{daytimeStageLn_s} );
|
|
$ret->{daytimeStage_float} =
|
|
$ret->{daytimeRel_s} / $ret->{daytimeStageLn_s};
|
|
$ret->{daytimeStage} =
|
|
int( ( ( $ret->{daytimeRel_s} + 1 ) / $ret->{daytimeStageLn_s} ) + 1 );
|
|
$ret->{daytimeStage} = 0
|
|
if ( $ret->{daytimeStage} < 1
|
|
|| $ret->{daytimeStage} > $ret->{daytimeStages} );
|
|
|
|
# change midnight event when season changes
|
|
$ret->{events}{ $ret->{midnight_t} }{VALUE} = 1
|
|
if ( $ret->{seasonMeteoChng} && $ret->{seasonMeteoChng} == 1 );
|
|
$ret->{events}{ $ret->{midnight_t} }{DESC} .=
|
|
", Begin meteorological $ret->{seasonMeteo_long} season"
|
|
if ( $ret->{seasonMeteoChng} && $ret->{seasonMeteoChng} == 1 );
|
|
$ret->{events}{ $ret->{midnight_t} }{VALUE} = 2
|
|
if ( $ret->{seasonAstroChng} && $ret->{seasonAstroChng} == 1 );
|
|
$ret->{events}{ $ret->{midnight_t} }{DESC} .=
|
|
", Begin astronomical $ret->{seasonAstro_long} season"
|
|
if ( $ret->{seasonAstroChng} && $ret->{seasonAstroChng} == 1 );
|
|
$ret->{events}{ $ret->{midnight_t} }{VALUE} = 2
|
|
if ( $ret->{seasonPhenoChng} && $ret->{seasonPhenoChng} == 1 );
|
|
$ret->{events}{ $ret->{midnight_t} }{DESC} .=
|
|
", Begin phenological season $ret->{seasonPheno_long}"
|
|
if ( $ret->{seasonPhenoChng} && $ret->{seasonPhenoChng} == 1 );
|
|
|
|
# calculate daytime from daytimeStage, season and DST
|
|
my $ds = $ret->{daytimeStage};
|
|
while ( !defined( $ret->{daytime} ) ) {
|
|
|
|
#TODO let user define %sdt2daytimes through attribute
|
|
$ret->{daytime} = $sdt2daytimes{1}{ $ret->{isdst} }{$ds}
|
|
if ( $sdt2daytimes{1}
|
|
&& $sdt2daytimes{1}{ $ret->{isdst} }
|
|
&& defined( $sdt2daytimes{1}{ $ret->{isdst} }{$ds} ) );
|
|
$ds--;
|
|
|
|
# when no relation was found
|
|
unless ( defined( $ret->{daytime} ) || $ds > -1 ) {
|
|
|
|
# assume midevening after sunset
|
|
if ( $ret->{time_s} >= $ret->{sunset_s} ) {
|
|
$ret->{daytime} = 5;
|
|
}
|
|
|
|
# assume night before sunrise
|
|
else {
|
|
$ret->{daytime} = 6;
|
|
}
|
|
}
|
|
}
|
|
|
|
# daytime during evening and night
|
|
unless ( $ret->{daytimeStage} ) {
|
|
$ret->{daytime} = 4 unless ( $ret->{daytime} > 4 );
|
|
$ret->{daytime} = 5 unless ( $ret->{daytime} > 5 || $ret->{isday} );
|
|
$ret->{daytime} = 6 if ( $ret->{time_s} < $ret->{sunrise_s} );
|
|
}
|
|
|
|
$ret->{daytime_long} = $daytimes{en}[ $ret->{daytime} ];
|
|
|
|
my @langs = ('EN');
|
|
push @langs, $lang unless ( $lang =~ /^EN/i );
|
|
foreach (@langs) {
|
|
my $l = lc($_);
|
|
$l =~ s/^([a-z]+).*/$1/g;
|
|
next unless ( $daytimes{$l} );
|
|
my $h = $l eq "en" ? $ret : \%{ $ret->{$_} };
|
|
|
|
$h->{daytime_long} = $daytimes{$l}[ $ret->{daytime} ];
|
|
}
|
|
|
|
# calculate daily schedule
|
|
#
|
|
|
|
# Midnight
|
|
$ret->{events}{ $ret->{midnight_t} }{TYPE} = "dayshift";
|
|
$ret->{events}{ $ret->{midnight_t} }{TIME} =
|
|
main::FmtDateTime( $ret->{midnight_t} );
|
|
$ret->{events}{ $ret->{midnight_t} }{DESC} =
|
|
"Begin of night time and new calendar day";
|
|
$ret->{events}{ $ret->{1}{midnight_t} }{TYPE} = "dayshift";
|
|
$ret->{events}{ $ret->{1}{midnight_t} }{TIME} =
|
|
$ret->{date} . " 24:00:00";
|
|
$ret->{events}{ $ret->{1}{midnight_t} }{DESC} =
|
|
"End of calendar day and begin night time";
|
|
|
|
# Holidays
|
|
$ret->{events}{ $ret->{midnight_t} }{DESC} .=
|
|
", $daystages{en}[2]: $ret->{day_desc}"
|
|
if ( $ret->{isholiday} );
|
|
$ret->{events}{ $ret->{1}{midnight_t} }{DESC} .=
|
|
", $daystages{en}[2]: $ret->{1}{day_desc}"
|
|
if ( $ret->{1}{isholiday} );
|
|
|
|
# DST change
|
|
#FIXME TODO
|
|
if ( $ret->{dstchange} && $ret->{dstchange} == 1 ) {
|
|
my $t = $ret->{midnight_t} + 2 * 60 * 60;
|
|
$ret->{events}{$t}{TYPE} = "dstshift";
|
|
$ret->{events}{$t}{VALUE} = $ret->{isdst};
|
|
$ret->{events}{$t}{TIME} = main::FmtDateTime($t);
|
|
$ret->{events}{$t}{DESC} = "Begin of standard time (-1h)"
|
|
unless ( $ret->{isdst} );
|
|
$ret->{events}{$t}{DESC} = "Begin of daylight saving time (+1h)"
|
|
if ( $ret->{isdst} );
|
|
}
|
|
|
|
# daytime stage event forecast for today
|
|
my $i = 1;
|
|
my $b = $ret->{sunrise_t};
|
|
while ( $i <= $ret->{daytimeStages} + 1 ) {
|
|
|
|
# find daytime
|
|
my $daytime;
|
|
$daytime = $sdt2daytimes{1}{ $ret->{isdst} }{$i}
|
|
if ( $sdt2daytimes{1}
|
|
&& $sdt2daytimes{1}{ $ret->{isdst} }
|
|
&& defined( $sdt2daytimes{1}{ $ret->{isdst} }{$i} ) );
|
|
|
|
# create event
|
|
my $t = int( $b + 0.5 );
|
|
$ret->{events}{$t}{TIME} = main::FmtDateTime($t);
|
|
if ( $i == $ret->{daytimeStages} + 1 ) {
|
|
$ret->{events}{$t}{TYPE} = "daytime";
|
|
$ret->{events}{$t}{VALUE} = "midevening";
|
|
$ret->{events}{$t}{DESC} = "End of daytime";
|
|
}
|
|
else {
|
|
$ret->{events}{$t}{TYPE} = "daytimeStage";
|
|
$ret->{events}{$t}{VALUE} = $i;
|
|
$ret->{events}{$t}{DESC} = "Begin of daytime stage $i"
|
|
unless ($daytime);
|
|
if ( defined($daytime) ) {
|
|
$ret->{events}{$t}{TYPE} = "daytime";
|
|
$ret->{events}{$t}{VALUE} = $daytimes{en}[$daytime];
|
|
$ret->{events}{$t}{DESC} =
|
|
"Begin of $daytimes{en}[$daytime] time and daytime stage $i";
|
|
}
|
|
}
|
|
$i++;
|
|
$b += $ret->{daytimeStageLn_s};
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
####################
|
|
# HELPER FUNCTIONS
|
|
|
|
sub decimal_mark ($;$) {
|
|
my $s;
|
|
my $i;
|
|
my $f;
|
|
if ( $_[0] =~ /^(\-|\+)?(\d+)(?:\.(\d+))?$/ ) {
|
|
$s = $1;
|
|
$i = reverse $2;
|
|
$f = $3;
|
|
}
|
|
else {
|
|
return $_[0];
|
|
}
|
|
|
|
my $locale = ( $_[1] ? $_[1] : undef );
|
|
|
|
my $old_locale = setlocale(LC_NUMERIC);
|
|
setlocale( LC_NUMERIC, $locale ) if ($locale);
|
|
use locale ':not_characters';
|
|
my ( $decimal_point, $thousands_sep, $grouping ) =
|
|
@{ localeconv() }{ 'decimal_point', 'thousands_sep', 'grouping' };
|
|
setlocale( LC_NUMERIC, "" );
|
|
setlocale( LC_NUMERIC, $old_locale );
|
|
no locale;
|
|
|
|
$decimal_point = '.'
|
|
unless ( defined($decimal_point) && $decimal_point ne '' );
|
|
$thousands_sep = chr(0x202F)
|
|
unless ( defined($thousands_sep) && $thousands_sep ne "" );
|
|
my @grouping =
|
|
$grouping && $grouping =~ /^\d+$/
|
|
? unpack( "C*", $grouping )
|
|
: (3);
|
|
|
|
$i =~ s/(\d{$grouping[0]})(?=\d)/$1$thousands_sep/g;
|
|
$f =~ s/(\d{$grouping[0]})(?=\d)/$1$thousands_sep/g
|
|
if ( defined($f) && $f ne '' );
|
|
|
|
return
|
|
( $s ? $s : '' )
|
|
. ( reverse $i )
|
|
. ( defined($f) && $f ne '' ? $decimal_point . $f : '' );
|
|
}
|
|
|
|
sub _round($;$) {
|
|
my ( $val, $n ) = @_;
|
|
$n = 1 unless ( defined($n) );
|
|
return sprintf( "%.${n}f", $val );
|
|
}
|
|
|
|
sub _time(;$$$$);
|
|
|
|
sub _time(;$$$$) {
|
|
my ( $time, $lang, $dayOffset, $params ) = @_;
|
|
$dayOffset = 1 if ( !defined($dayOffset) || $dayOffset !~ /^-?\d+$/ );
|
|
$lang = (
|
|
$main::attr{global}{language}
|
|
? $main::attr{global}{language}
|
|
: "EN"
|
|
) unless ($lang);
|
|
|
|
return undef
|
|
unless ( !$time || $time =~ /^\d{10}(?:\.\d+)?$/ );
|
|
|
|
my %ret;
|
|
$ret{time_t} = $time if ($time);
|
|
$ret{time_t} = time unless ($time);
|
|
$ret{params} = $params if ($params);
|
|
|
|
my @t = localtime( $ret{time_t} );
|
|
(
|
|
$ret{sec}, $ret{min}, $ret{hour}, $ret{mday}, $ret{mon},
|
|
$ret{year}, $ret{wday}, $ret{yday}, $ret{isdst}
|
|
) = @t;
|
|
$ret{monISO} = $ret{mon} + 1;
|
|
$ret{year} += 1900;
|
|
|
|
$ret{date} =
|
|
sprintf( "%04d-%02d-%02d", $ret{year}, $ret{monISO}, $ret{mday} );
|
|
$ret{time} = sprintf( "%02d:%02d", $ret{hour}, $ret{min} );
|
|
$ret{time_hms} =
|
|
sprintf( "%02d:%02d:%02d", $ret{hour}, $ret{min}, $ret{sec} );
|
|
$ret{time_s} = hms2s( $ret{time_hms} ); #FIXME for DST change
|
|
$ret{datetime} = $ret{date} . " " . $ret{time_hms};
|
|
$ret{midnight_t} = $ret{time_t} - $ret{time_s}; #FIXME for DST change
|
|
|
|
# get leap year status
|
|
$ret{isly} = IsLeapYear( $ret{year} );
|
|
|
|
# remaining monthdays
|
|
$ret{mdayrem} = 0;
|
|
$ret{mdayrem} = 31 - $ret{mday} if ( $ret{monISO} == 1 );
|
|
$ret{mdayrem} = 28 + $ret{isly} - $ret{mday}
|
|
if ( $ret{monISO} == 2 );
|
|
$ret{mdayrem} = 31 - $ret{mday} if ( $ret{monISO} == 3 );
|
|
$ret{mdayrem} = 30 - $ret{mday} if ( $ret{monISO} == 4 );
|
|
$ret{mdayrem} = 31 - $ret{mday} if ( $ret{monISO} == 5 );
|
|
$ret{mdayrem} = 30 - $ret{mday} if ( $ret{monISO} == 6 );
|
|
$ret{mdayrem} = 31 - $ret{mday} if ( $ret{monISO} == 7 );
|
|
$ret{mdayrem} = 31 - $ret{mday} if ( $ret{monISO} == 8 );
|
|
$ret{mdayrem} = 30 - $ret{mday} if ( $ret{monISO} == 9 );
|
|
$ret{mdayrem} = 31 - $ret{mday} if ( $ret{monISO} == 10 );
|
|
$ret{mdayrem} = 30 - $ret{mday} if ( $ret{monISO} == 11 );
|
|
$ret{mdayrem} = 31 - $ret{mday} if ( $ret{monISO} == 12 );
|
|
|
|
# remaining yeardays
|
|
$ret{ydayrem} = 365 + $ret{isly} - $ret{yday};
|
|
|
|
# ISO 8601 weekday as number with Monday as 1 (1-7)
|
|
$ret{wdaynISO} = strftime( '%u', @t );
|
|
|
|
# Week number with the first Sunday as the first day of week one (00-53)
|
|
$ret{week} = strftime( '%U', @t );
|
|
|
|
# ISO 8601 week number (00-53)
|
|
$ret{weekISO} = strftime( '%V', @t );
|
|
|
|
# weekend
|
|
$ret{iswe} = ( $ret{wday} == 0 || $ret{wday} == 6 ) ? 1 : 0;
|
|
|
|
# text strings
|
|
my @langs = ('EN');
|
|
push @langs, $lang unless ( $lang =~ /^EN/i );
|
|
foreach (@langs) {
|
|
my $l = lc($_);
|
|
$l =~ s/^([a-z]+).*/$1/g;
|
|
next unless ( $months{$l} );
|
|
my $h = $l eq "en" ? \%ret : \%{ $ret{$_} };
|
|
|
|
$h->{dst_long} = $dst{$l}[ $ret{isdst} ];
|
|
$h->{rday_long} = $reldays{$l}[1];
|
|
$h->{day_desc} = $daystages{$l}[ $ret{iswe} ];
|
|
$h->{wday_long} = $days{$l}[ $ret{wday} ];
|
|
$h->{wday_short} = $dayss{$l}[ $ret{wday} ];
|
|
$h->{mon_long} = $months{$l}[ $ret{mon} ];
|
|
$h->{mon_short} = $monthss{$l}[ $ret{mon} ];
|
|
|
|
$h->{date_long} =
|
|
_ReplaceStringByHashKey( \%ret, $dateformats{$l}, $_ );
|
|
$h->{date_short} =
|
|
_ReplaceStringByHashKey( \%ret, $dateformatss{$l}, $_ );
|
|
}
|
|
|
|
# holiday
|
|
if ($dayOffset) {
|
|
$ret{'-1'}{isholiday} = 0;
|
|
$ret{1}{isholiday} = 0;
|
|
}
|
|
$ret{isholiday} = 0;
|
|
|
|
my $tod;
|
|
my $ytd;
|
|
my $tom;
|
|
if ( main::AttrVal( 'global', 'holiday2we', undef ) ) {
|
|
my $date = sprintf( "%02d-%02d", $ret{monISO}, $ret{mday} );
|
|
$tod = main::IsWe($date);
|
|
if ($dayOffset) {
|
|
$ytd = main::IsWe('yesterday');
|
|
$tom = main::IsWe('tomorrow');
|
|
}
|
|
|
|
if ( $tod ne "none" ) {
|
|
$ret{iswe} += 2;
|
|
$ret{isholiday} = 1;
|
|
$ret{day_desc} = $tod;
|
|
|
|
foreach (@langs) {
|
|
my $l = lc($_);
|
|
$l =~ s/^([a-z]+).*/$1/g;
|
|
next unless ( $months{$l} );
|
|
my $h = $l eq "en" ? \%ret : \%{ $ret{$_} };
|
|
|
|
$h->{day_desc} = $tod;
|
|
}
|
|
}
|
|
if ($dayOffset) {
|
|
if ( $ytd ne "none" && $ret{'-1'} ) {
|
|
$ret{'-1'}{isholiday} = 1;
|
|
$ret{'-1'}{day_desc} = $ytd;
|
|
|
|
foreach (@langs) {
|
|
my $l = lc($_);
|
|
$l =~ s/^([a-z]+).*/$1/g;
|
|
next unless ( $months{$l} );
|
|
my $h = $l eq "en" ? $ret{'-1'} : \%{ $ret{'-1'}{$_} };
|
|
|
|
$h->{day_desc} = $ytd;
|
|
}
|
|
}
|
|
if ( $tom ne "none" && $ret{1} ) {
|
|
$ret{1}{isholiday} = 1;
|
|
$ret{1}{day_desc} = $tom;
|
|
|
|
foreach (@langs) {
|
|
my $l = lc($_);
|
|
$l =~ s/^([a-z]+).*/$1/g;
|
|
next unless ( $months{$l} );
|
|
my $h = $l eq "en" ? $ret{1} : \%{ $ret{1}{$_} };
|
|
|
|
$h->{day_desc} = $tom;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (wantarray) {
|
|
my @a;
|
|
|
|
foreach (
|
|
'sec', 'min', 'hour', 'mday', 'mon',
|
|
'year', 'wday', 'wdayn', 'yday', 'isdst',
|
|
'mdayrem', 'monISO', 'week', 'weekISO', 'wdayISO',
|
|
'wdaynISO', 'ydayrem', 'time_t', 'datetime', 'date',
|
|
'time_hms', 'time', 'isly',
|
|
)
|
|
{
|
|
push @a, $ret{$_};
|
|
}
|
|
|
|
return @a;
|
|
}
|
|
|
|
elsif ($dayOffset) {
|
|
my $i = $dayOffset * -1;
|
|
while ( $i < $dayOffset + 1 ) {
|
|
$ret{$i} = _time( $ret{time_t} + ( 24 * 60 * 60 * $i ), $lang, 0 )
|
|
unless ( $i == 0 );
|
|
|
|
foreach (@langs) {
|
|
my $l = $_;
|
|
$l =~ s/^([A-Z-a-z]+).*/$1/g;
|
|
$l = lc($l);
|
|
next if ( $i == 0 || !$reldays{$l} );
|
|
my $h = $l eq "en" ? \%{ $ret{$i} } : \%{ $ret{$i}{$l} };
|
|
|
|
if ( $i == -1 || $i == 1 ) {
|
|
$h->{rday_long} = $reldays{$l}[ $i + 1 ];
|
|
}
|
|
else {
|
|
delete $h->{rday_long};
|
|
}
|
|
}
|
|
|
|
$i++;
|
|
}
|
|
|
|
# DST change
|
|
$ret{'-1'}{dstchange} = 0;
|
|
$ret{dstchange} = 0;
|
|
$ret{1}{dstchange} = 0;
|
|
|
|
if ( $ret{isdst} ne $ret{1}{isdst} ) {
|
|
$ret{dstchange} = 2;
|
|
$ret{1}{dstchange} = 1;
|
|
}
|
|
elsif ( $ret{isdst} ne $ret{'-1'}{isdst} ) {
|
|
$ret{'-1'}{dstchange} = 2;
|
|
$ret{dstchange} = 1;
|
|
}
|
|
}
|
|
|
|
return \%ret;
|
|
}
|
|
|
|
sub _GetIndexFromArray($$) {
|
|
my ( $string, $array ) = @_;
|
|
return undef unless ( ref($array) eq "ARRAY" );
|
|
my ($index) = grep { $array->[$_] =~ /^$string$/i } ( 0 .. @$array - 1 );
|
|
return defined $index ? $index : undef;
|
|
}
|
|
|
|
sub _ReplaceStringByHashKey($$;$) {
|
|
my ( $hash, $string, $sublvl ) = @_;
|
|
return $string unless ( $hash && ref($hash) eq "HASH" );
|
|
|
|
$string = _ReplaceStringByHashKey( $hash->{$sublvl}, $string )
|
|
if ( $sublvl && $hash->{$sublvl} );
|
|
|
|
foreach my $key ( keys %{$hash} ) {
|
|
next if ( ref( $hash->{$key} ) );
|
|
my $val = $hash->{$key};
|
|
$string =~ s/%$key%/$val/gi;
|
|
$string =~ s/\$$key/$val/g;
|
|
}
|
|
|
|
return $string;
|
|
}
|
|
|
|
1;
|
|
|
|
=pod
|
|
=encoding utf8
|
|
|
|
=for :application/json;q=META.json UConv.pm
|
|
{
|
|
"author": [
|
|
"Julian Pawlowski <julian.pawlowski@gmail.com>"
|
|
],
|
|
"x_fhem_maintainer": [
|
|
"loredo"
|
|
],
|
|
"x_fhem_maintainer_github": [
|
|
"jpawlowski"
|
|
],
|
|
"keywords": [
|
|
"RType",
|
|
"Unit"
|
|
]
|
|
}
|
|
=end :application/json;q=META.json
|
|
|
|
=cut
|