core.utilities.statistics_hourly_data_by_period()   F
last analyzed

Complexity

Conditions 60

Size

Total Lines 308
Code Lines 252

Duplication

Lines 121
Ratio 39.29 %

Importance

Changes 0
Metric Value
eloc 252
dl 121
loc 308
rs 0
c 0
b 0
f 0
cc 60
nop 4

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like core.utilities.statistics_hourly_data_by_period() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
import collections
2
import statistics
3
from datetime import datetime, timedelta
4
from decimal import Decimal
5
import mysql.connector
6
import config
7
import gettext
8
9
10
########################################################################################################################
11
# Aggregate hourly data by period
12
#
13
# This function aggregates hourly energy data into different time periods (hourly, daily, weekly, monthly, yearly).
14
# It processes raw hourly data and groups it according to the specified period type for reporting and analysis.
15
#
16
# Args:
17
#     rows_hourly: List of tuples containing (start_datetime_utc, actual_value) for hourly data
18
#                  Should belong to one energy_category_id
19
#     start_datetime_utc: Start datetime in UTC for the aggregation period
20
#     end_datetime_utc: End datetime in UTC for the aggregation period
21
#     period_type: Period type for aggregation - 'hourly', 'daily', 'weekly', 'monthly', or 'yearly'
22
#
23
# Returns:
24
#     List of tuples containing (datetime_utc, aggregated_value) for the specified period type
25
#
26
# Note: This procedure doesn't work with multiple energy categories
27
########################################################################################################################
28
def aggregate_hourly_data_by_period(rows_hourly, start_datetime_utc, end_datetime_utc, period_type):
29
    # Validate input parameters
30
    if start_datetime_utc is None or \
31
            end_datetime_utc is None or \
32
            start_datetime_utc >= end_datetime_utc or \
33
            period_type not in ('hourly', 'daily', 'weekly', 'monthly', 'yearly'):
34
        return list()
35
36
    # Remove timezone info for consistent processing
37
    start_datetime_utc = start_datetime_utc.replace(tzinfo=None)
38
    end_datetime_utc = end_datetime_utc.replace(tzinfo=None)
39
40
    # Process hourly aggregation
41
    if period_type == "hourly":
42
        result_rows_hourly = list()
43
        # TODO: add config.working_day_start_time_local
44
        # TODO: add config.minutes_to_count
45
        current_datetime_utc = start_datetime_utc.replace(minute=0, second=0, microsecond=0, tzinfo=None)
46
        while current_datetime_utc <= end_datetime_utc:
47
            subtotal = None
48
            # Sum values within the current hour period
49
            for row in rows_hourly:
50
                if current_datetime_utc <= row[0] < current_datetime_utc + \
51
                        timedelta(minutes=config.minutes_to_count):
52
                    if row[1] is not None:
53
                        if subtotal is None:
54
                            subtotal = row[1]
55
                        else:
56
                            subtotal += row[1]
57
            result_rows_hourly.append((current_datetime_utc, subtotal))
58
            current_datetime_utc += timedelta(minutes=config.minutes_to_count)
59
60
        return result_rows_hourly
61
62
    # Process daily aggregation
63
    elif period_type == "daily":
64
        result_rows_daily = list()
65
        # TODO: add config.working_day_start_time_local
66
        # TODO: add config.minutes_to_count
67
        # Calculate the start datetime in UTC of the first day in local timezone
68
        start_datetime_local = start_datetime_utc + timedelta(hours=int(config.utc_offset[1:3]))
69
        current_datetime_utc = start_datetime_local.replace(hour=0) - timedelta(hours=int(config.utc_offset[1:3]))
70
        while current_datetime_utc <= end_datetime_utc:
71
            subtotal = None
72
            for row in rows_hourly:
73
                if current_datetime_utc <= row[0] < current_datetime_utc + timedelta(days=1):
74
                    if row[1] is not None:
75
                        if subtotal is None:
76
                            subtotal = row[1]
77
                        else:
78
                            subtotal += row[1]
79
            result_rows_daily.append((current_datetime_utc, subtotal))
80
            current_datetime_utc += timedelta(days=1)
81
82
        return result_rows_daily
83
84
    elif period_type == 'weekly':
85
        result_rows_weekly = list()
86
        # todo: add config.working_day_start_time_local
87
        # todo: add config.minutes_to_count
88
        # calculate the start datetime in utc of the monday in the first week in local
89
        start_datetime_local = start_datetime_utc + timedelta(hours=int(config.utc_offset[1:3]))
90
        weekday = start_datetime_local.weekday()
91
        current_datetime_utc = \
92
            start_datetime_local.replace(hour=0) - timedelta(days=weekday, hours=int(config.utc_offset[1:3]))
93
        while current_datetime_utc <= end_datetime_utc:
94
95
            next_datetime_utc = current_datetime_utc + timedelta(days=7)
96
            subtotal = None
97
            for row in rows_hourly:
98
                if current_datetime_utc <= row[0] < next_datetime_utc:
99
                    if row[1] is not None:
100
                        if subtotal is None:
101
                            subtotal = row[1]
102
                        else:
103
                            subtotal += row[1]
104
            result_rows_weekly.append((current_datetime_utc, subtotal))
105
            current_datetime_utc = next_datetime_utc
106
107
        return result_rows_weekly
108
109
    elif period_type == "monthly":
110
        result_rows_monthly = list()
111
        # todo: add config.working_day_start_time_local
112
        # todo: add config.minutes_to_count
113
        # calculate the start datetime the first day in the first month in local
114
        start_datetime_local = start_datetime_utc + timedelta(hours=int(config.utc_offset[1:3]))
115
        current_datetime_local = start_datetime_local.replace(day=1, hour=0, minute=0,
116
                                                              second=0, microsecond=0)
117
        end_datetime_local = end_datetime_utc + timedelta(hours=int(config.utc_offset[1:3]))
118
        while current_datetime_local <= end_datetime_local:
119
            # calculate the next datetime in local
120
            if current_datetime_local.month < 12:
121
                next_datetime_local = datetime(year=current_datetime_local.year,
122
                                               month=current_datetime_local.month + 1,
123
                                               day=1, hour=0, minute=0, second=0, microsecond=0, tzinfo=None)
124
            elif current_datetime_local.month == 12:
125
                next_datetime_local = datetime(year=current_datetime_local.year + 1,
126
                                               month=1,
127
                                               day=1, hour=0, minute=0, second=0, microsecond=0, tzinfo=None)
128
            current_datetime_utc = current_datetime_local - timedelta(hours=int(config.utc_offset[1:3]))
129
            next_datetime_utc = next_datetime_local - timedelta(hours=int(config.utc_offset[1:3]))
0 ignored issues
show
introduced by
The variable next_datetime_local does not seem to be defined for all execution paths.
Loading history...
130
            subtotal = None
131
            for row in rows_hourly:
132
                if current_datetime_utc <= row[0] < next_datetime_utc:
133
                    if row[1] is not None:
134
                        if subtotal is None:
135
                            subtotal = row[1]
136
                        else:
137
                            subtotal += row[1]
138
139
            result_rows_monthly.append((current_datetime_utc, subtotal))
140
            current_datetime_local = next_datetime_local
141
142
        return result_rows_monthly
143
144
    elif period_type == "yearly":
145
        result_rows_yearly = list()
146
        # todo: add config.working_day_start_time_local
147
        # todo: add config.minutes_to_count
148
        # calculate the start datetime in utc of the first day in the first year in local
149
        start_datetime_local = start_datetime_utc + timedelta(hours=int(config.utc_offset[1:3]))
150
        current_datetime_utc = start_datetime_local.replace(month=1, day=1, hour=0) - timedelta(
151
            hours=int(config.utc_offset[1:3]))
152
153
        while current_datetime_utc <= end_datetime_utc:
154
            # calculate the next datetime in utc
155
            # todo: timedelta of year
156
            next_datetime_utc = datetime(year=current_datetime_utc.year + 2,
157
                                         month=1,
158
                                         day=1,
159
                                         hour=current_datetime_utc.hour,
160
                                         minute=current_datetime_utc.minute,
161
                                         second=current_datetime_utc.second,
162
                                         microsecond=current_datetime_utc.microsecond,
163
                                         tzinfo=current_datetime_utc.tzinfo) - timedelta(days=1)
164
            subtotal = None
165
            for row in rows_hourly:
166
                if current_datetime_utc <= row[0] < next_datetime_utc:
167
                    if row[1] is not None:
168
                        if subtotal is None:
169
                            subtotal = row[1]
170
                        else:
171
                            subtotal += row[1]
172
173
            result_rows_yearly.append((current_datetime_utc, subtotal))
174
            current_datetime_utc = next_datetime_utc
175
        return result_rows_yearly
176
    else:
177
        return list()
178
179
180
########################################################################################################################
181
# Get tariffs by energy category
182
########################################################################################################################
183 View Code Duplication
def get_energy_category_tariffs(cost_center_id, energy_category_id, start_datetime_utc, end_datetime_utc):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
184
    # todo: validate parameters
185
    if cost_center_id is None:
186
        return dict()
187
188
    start_datetime_utc = start_datetime_utc.replace(tzinfo=None)
189
    end_datetime_utc = end_datetime_utc.replace(tzinfo=None)
190
191
    # get timezone offset in minutes, this value will be returned to client
192
    timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6])
193
    if config.utc_offset[0] == '-':
194
        timezone_offset = -timezone_offset
195
196
    tariff_dict = collections.OrderedDict()
197
198
    cnx = None
199
    cursor = None
200
    try:
201
        cnx = mysql.connector.connect(**config.myems_system_db)
202
        cursor = cnx.cursor()
203
        query_tariffs = (" SELECT t.id, t.valid_from_datetime_utc, t.valid_through_datetime_utc "
204
                         " FROM tbl_tariffs t, tbl_cost_centers_tariffs cct "
205
                         " WHERE t.energy_category_id = %s AND "
206
                         "       t.id = cct.tariff_id AND "
207
                         "       cct.cost_center_id = %s AND "
208
                         "       t.valid_through_datetime_utc >= %s AND "
209
                         "       t.valid_from_datetime_utc <= %s "
210
                         " ORDER BY t.valid_from_datetime_utc ")
211
        cursor.execute(query_tariffs, (energy_category_id, cost_center_id, start_datetime_utc, end_datetime_utc,))
212
        rows_tariffs = cursor.fetchall()
213
    except InterfaceError as ex:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable InterfaceError does not seem to be defined.
Loading history...
214
        print("Failed to connect request")
215
        if cnx:
216
            cnx.close()
217
        if cursor:
218
            cursor.close()
219
        return dict()
220
    except OperationalError as ex:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable OperationalError does not seem to be defined.
Loading history...
221
        print("Failed to operate request")
222
        if cnx:
223
            cnx.close()
224
        if cursor:
225
            cursor.close()
226
        return dict()
227
    except ProgrammingError as ex:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable ProgrammingError does not seem to be defined.
Loading history...
228
        print("Failed to SQL request")
229
        if cnx:
230
            cnx.close()
231
        if cursor:
232
            cursor.close()
233
        return dict()
234
    except DataError as ex:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable DataError does not seem to be defined.
Loading history...
235
        print("Failed to SQL Data request")
236
        if cnx:
237
            cnx.close()
238
        if cursor:
239
            cursor.close()
240
        return dict()
241
    except Exception as ex:
242
        print('write_log:' + str(ex))
243
        if cnx:
244
            cnx.close()
245
        if cursor:
246
            cursor.close()
247
        return dict()
248
249
    if rows_tariffs is None or len(rows_tariffs) == 0:
250
        if cursor:
251
            cursor.close()
252
        if cnx:
253
            cnx.close()
254
        return dict()
255
256
    for row in rows_tariffs:
257
        tariff_dict[row[0]] = {'valid_from_datetime_utc': row[1],
258
                               'valid_through_datetime_utc': row[2],
259
                               'rates': list()}
260
261
    try:
262
        query_timeofuse_tariffs = (" SELECT tariff_id, start_time_of_day, end_time_of_day, price "
263
                                   " FROM tbl_tariffs_timeofuses "
264
                                   " WHERE tariff_id IN ( " + ', '.join(map(str, tariff_dict.keys())) + ")"
265
                                   " ORDER BY tariff_id, start_time_of_day ")
266
        cursor.execute(query_timeofuse_tariffs, )
267
        rows_timeofuse_tariffs = cursor.fetchall()
268
    except InterfaceError as ex:
269
        print("Failed to connect request")
270
        if cnx:
271
            cnx.close()
272
        if cursor:
273
            cursor.close()
274
        return dict()
275
    except OperationalError as ex:
276
        print("Failed to operate request")
277
        if cnx:
278
            cnx.close()
279
        if cursor:
280
            cursor.close()
281
        return dict()
282
    except ProgrammingError as ex:
283
        print("Failed to SQL request")
284
        if cnx:
285
            cnx.close()
286
        if cursor:
287
            cursor.close()
288
        return dict()
289
    except DataError as ex:
290
        print("Failed to SQL Data request")
291
        if cnx:
292
            cnx.close()
293
        if cursor:
294
            cursor.close()
295
        return dict()
296
    except Exception as ex:
297
        print('write_log:' + str(ex))
298
        if cnx:
299
            cnx.close()
300
        if cursor:
301
            cursor.close()
302
        return dict()
303
304
    if cursor:
305
        cursor.close()
306
    if cnx:
307
        cnx.close()
308
309
    if rows_timeofuse_tariffs is None or len(rows_timeofuse_tariffs) == 0:
310
        return dict()
311
312
    for row in rows_timeofuse_tariffs:
313
        tariff_dict[row[0]]['rates'].append({'start_time_of_day': row[1],
314
                                             'end_time_of_day': row[2],
315
                                             'price': row[3]})
316
317
    result = dict()
318
    for tariff_id, tariff_value in tariff_dict.items():
319
        current_datetime_utc = tariff_value['valid_from_datetime_utc']
320
        while current_datetime_utc < tariff_value['valid_through_datetime_utc']:
321
            for rate in tariff_value['rates']:
322
                current_datetime_local = current_datetime_utc + timedelta(minutes=timezone_offset)
323
                seconds_since_midnight = (current_datetime_local -
324
                                          current_datetime_local.replace(hour=0,
325
                                                                         second=0,
326
                                                                         microsecond=0,
327
                                                                         tzinfo=None)).total_seconds()
328
                if rate['start_time_of_day'].total_seconds() <= \
329
                        seconds_since_midnight < rate['end_time_of_day'].total_seconds():
330
                    result[current_datetime_utc] = rate['price']
331
                    break
332
333
            # start from the next time slot
334
            current_datetime_utc += timedelta(minutes=config.minutes_to_count)
335
336
    return {k: v for k, v in result.items() if start_datetime_utc <= k <= end_datetime_utc}
337
338
339
########################################################################################################################
340
# Get peak types of tariff by energy category
341
# peak types: toppeak, onpeak, midpeak, offpeak, deep
342
########################################################################################################################
343 View Code Duplication
def get_energy_category_peak_types(cost_center_id, energy_category_id, start_datetime_utc, end_datetime_utc):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
344
    # todo: validate parameters
345
    if cost_center_id is None:
346
        return dict()
347
348
    start_datetime_utc = start_datetime_utc.replace(tzinfo=None)
349
    end_datetime_utc = end_datetime_utc.replace(tzinfo=None)
350
351
    # get timezone offset in minutes, this value will be returned to client
352
    timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6])
353
    if config.utc_offset[0] == '-':
354
        timezone_offset = -timezone_offset
355
356
    tariff_dict = collections.OrderedDict()
357
358
    cnx = None
359
    cursor = None
360
    try:
361
        cnx = mysql.connector.connect(**config.myems_system_db)
362
        cursor = cnx.cursor()
363
        query_tariffs = (" SELECT t.id, t.valid_from_datetime_utc, t.valid_through_datetime_utc "
364
                         " FROM tbl_tariffs t, tbl_cost_centers_tariffs cct "
365
                         " WHERE t.energy_category_id = %s AND "
366
                         "       t.id = cct.tariff_id AND "
367
                         "       cct.cost_center_id = %s AND "
368
                         "       t.valid_through_datetime_utc >= %s AND "
369
                         "       t.valid_from_datetime_utc <= %s "
370
                         " ORDER BY t.valid_from_datetime_utc ")
371
        cursor.execute(query_tariffs, (energy_category_id, cost_center_id, start_datetime_utc, end_datetime_utc,))
372
        rows_tariffs = cursor.fetchall()
373
    except InterfaceError as ex:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable InterfaceError does not seem to be defined.
Loading history...
374
        print("Failed to connect request")
375
        if cnx:
376
            cnx.close()
377
        if cursor:
378
            cursor.close()
379
        return dict()
380
    except OperationalError as ex:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable OperationalError does not seem to be defined.
Loading history...
381
        print("Failed to operate request")
382
        if cnx:
383
            cnx.close()
384
        if cursor:
385
            cursor.close()
386
        return dict()
387
    except ProgrammingError as ex:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable ProgrammingError does not seem to be defined.
Loading history...
388
        print("Failed to SQL request")
389
        if cnx:
390
            cnx.close()
391
        if cursor:
392
            cursor.close()
393
        return dict()
394
    except DataError as ex:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable DataError does not seem to be defined.
Loading history...
395
        print("Failed to SQL Data request")
396
        if cnx:
397
            cnx.close()
398
        if cursor:
399
            cursor.close()
400
        return dict()
401
    except Exception as ex:
402
        print('write_log:' + str(ex))
403
        if cnx:
404
            cnx.close()
405
        if cursor:
406
            cursor.close()
407
        return dict()
408
409
    if rows_tariffs is None or len(rows_tariffs) == 0:
410
        if cursor:
411
            cursor.close()
412
        if cnx:
413
            cnx.close()
414
        return dict()
415
416
    for row in rows_tariffs:
417
        tariff_dict[row[0]] = {'valid_from_datetime_utc': row[1],
418
                               'valid_through_datetime_utc': row[2],
419
                               'rates': list()}
420
421
    try:
422
        query_timeofuse_tariffs = (" SELECT tariff_id, start_time_of_day, end_time_of_day, peak_type "
423
                                   " FROM tbl_tariffs_timeofuses "
424
                                   " WHERE tariff_id IN ( " + ', '.join(map(str, tariff_dict.keys())) + ")"
425
                                   " ORDER BY tariff_id, start_time_of_day ")
426
        cursor.execute(query_timeofuse_tariffs, )
427
        rows_timeofuse_tariffs = cursor.fetchall()
428
    except InterfaceError as ex:
429
        print("Failed to connect request")
430
        if cnx:
431
            cnx.close()
432
        if cursor:
433
            cursor.close()
434
        return dict()
435
    except OperationalError as ex:
436
        print("Failed to operate request")
437
        if cnx:
438
            cnx.close()
439
        if cursor:
440
            cursor.close()
441
        return dict()
442
    except ProgrammingError as ex:
443
        print("Failed to SQL request")
444
        if cnx:
445
            cnx.close()
446
        if cursor:
447
            cursor.close()
448
        return dict()
449
    except DataError as ex:
450
        print("Failed to SQL Data request")
451
        if cnx:
452
            cnx.close()
453
        if cursor:
454
            cursor.close()
455
        return dict()
456
    except Exception as ex:
457
        print('write_log:' + str(ex))
458
        if cnx:
459
            cnx.close()
460
        if cursor:
461
            cursor.close()
462
        return dict()
463
464
    if cursor:
465
        cursor.close()
466
    if cnx:
467
        cnx.close()
468
469
    if rows_timeofuse_tariffs is None or len(rows_timeofuse_tariffs) == 0:
470
        return dict()
471
472
    for row in rows_timeofuse_tariffs:
473
        tariff_dict[row[0]]['rates'].append({'start_time_of_day': row[1],
474
                                             'end_time_of_day': row[2],
475
                                             'peak_type': row[3]})
476
477
    result = dict()
478
    for tariff_id, tariff_value in tariff_dict.items():
479
        current_datetime_utc = tariff_value['valid_from_datetime_utc']
480
        while current_datetime_utc < tariff_value['valid_through_datetime_utc']:
481
            for rate in tariff_value['rates']:
482
                current_datetime_local = current_datetime_utc + timedelta(minutes=timezone_offset)
483
                seconds_since_midnight = (current_datetime_local -
484
                                          current_datetime_local.replace(hour=0,
485
                                                                         second=0,
486
                                                                         microsecond=0,
487
                                                                         tzinfo=None)).total_seconds()
488
                if rate['start_time_of_day'].total_seconds() <= \
489
                        seconds_since_midnight < rate['end_time_of_day'].total_seconds():
490
                    result[current_datetime_utc] = rate['peak_type']
491
                    break
492
493
            # start from the next time slot
494
            current_datetime_utc += timedelta(minutes=config.minutes_to_count)
495
496
    return {k: v for k, v in result.items() if start_datetime_utc <= k <= end_datetime_utc}
497
498
499
########################################################################################################################
500
# Averaging calculator of hourly data by period
501
#   rows_hourly: list of (start_datetime_utc, actual_value), should belong to one energy_category_id
502
#   start_datetime_utc: start datetime in utc
503
#   end_datetime_utc: end datetime in utc
504
#   period_type: use one of the period types, 'hourly', 'daily', 'weekly', 'monthly' and 'yearly'
505
# Returns: periodically data of average and maximum
506
# Note: this procedure doesn't work with multiple energy categories
507
########################################################################################################################
508
def averaging_hourly_data_by_period(rows_hourly, start_datetime_utc, end_datetime_utc, period_type):
509
    # todo: validate parameters
510
    if start_datetime_utc is None or \
511
            end_datetime_utc is None or \
512
            start_datetime_utc >= end_datetime_utc or \
513
            period_type not in ('hourly', 'daily', 'weekly', 'monthly', 'yearly'):
514
        return list(), None, None
515
516
    start_datetime_utc = start_datetime_utc.replace(tzinfo=None)
517
    end_datetime_utc = end_datetime_utc.replace(tzinfo=None)
518
519
    if period_type == "hourly":
520
        result_rows_hourly = list()
521
        # todo: add config.working_day_start_time_local
522
        # todo: add config.minutes_to_count
523
        total = Decimal(0.0)
524
        maximum = None
525
        counter = 0
526
        current_datetime_utc = start_datetime_utc.replace(minute=0, second=0, microsecond=0, tzinfo=None)
527 View Code Duplication
        while current_datetime_utc <= end_datetime_utc:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
528
            sub_total = Decimal(0.0)
529
            sub_maximum = None
530
            sub_counter = 0
531
            for row in rows_hourly:
532
                if current_datetime_utc <= row[0] < current_datetime_utc + \
533
                        timedelta(minutes=config.minutes_to_count):
534
                    sub_total += row[1]
535
                    if sub_maximum is None:
536
                        sub_maximum = row[1]
537
                    elif sub_maximum < row[1]:
538
                        sub_maximum = row[1]
539
                    sub_counter += 1
540
541
            sub_average = (sub_total / sub_counter) if sub_counter > 0 else None
542
            result_rows_hourly.append((current_datetime_utc, sub_average, sub_maximum))
543
544
            total += sub_total
545
            counter += sub_counter
546
            if sub_maximum is None:
547
                pass
548
            elif maximum is None:
549
                maximum = sub_maximum
550
            elif maximum < sub_maximum:
551
                maximum = sub_maximum
552
553
            current_datetime_utc += timedelta(minutes=config.minutes_to_count)
554
555
        average = total / counter if counter > 0 else None
556
        return result_rows_hourly, average, maximum
557
558
    elif period_type == "daily":
559
        result_rows_daily = list()
560
        # todo: add config.working_day_start_time_local
561
        # todo: add config.minutes_to_count
562
        total = Decimal(0.0)
563
        maximum = None
564
        counter = 0
565
        # calculate the start datetime in utc of the first day in local
566
        start_datetime_local = start_datetime_utc + timedelta(hours=int(config.utc_offset[1:3]))
567
        current_datetime_utc = start_datetime_local.replace(hour=0) - timedelta(hours=int(config.utc_offset[1:3]))
568 View Code Duplication
        while current_datetime_utc <= end_datetime_utc:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
569
            sub_total = Decimal(0.0)
570
            sub_maximum = None
571
            sub_counter = 0
572
            for row in rows_hourly:
573
                if current_datetime_utc <= row[0] < current_datetime_utc + timedelta(days=1):
574
                    sub_total += row[1]
575
                    if sub_maximum is None:
576
                        sub_maximum = row[1]
577
                    elif sub_maximum < row[1]:
578
                        sub_maximum = row[1]
579
                    sub_counter += 1
580
581
            sub_average = (sub_total / sub_counter) if sub_counter > 0 else None
582
            result_rows_daily.append((current_datetime_utc, sub_average, sub_maximum))
583
            total += sub_total
584
            counter += sub_counter
585
            if sub_maximum is None:
586
                pass
587
            elif maximum is None:
588
                maximum = sub_maximum
589
            elif maximum < sub_maximum:
590
                maximum = sub_maximum
591
            current_datetime_utc += timedelta(days=1)
592
593
        average = total / counter if counter > 0 else None
594
        return result_rows_daily, average, maximum
595
596
    elif period_type == 'weekly':
597
        result_rows_weekly = list()
598
        # todo: add config.working_day_start_time_local
599
        # todo: add config.minutes_to_count
600
        total = Decimal(0.0)
601
        maximum = None
602
        counter = 0
603
        # calculate the start datetime in utc of the monday in the first week in local
604
        start_datetime_local = start_datetime_utc + timedelta(hours=int(config.utc_offset[1:3]))
605
        weekday = start_datetime_local.weekday()
606
        current_datetime_utc = \
607
            start_datetime_local.replace(hour=0) - timedelta(days=weekday, hours=int(config.utc_offset[1:3]))
608 View Code Duplication
        while current_datetime_utc <= end_datetime_utc:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
609
            sub_total = Decimal(0.0)
610
            sub_maximum = None
611
            sub_counter = 0
612
            for row in rows_hourly:
613
                if current_datetime_utc <= row[0] < current_datetime_utc + timedelta(days=7):
614
                    sub_total += row[1]
615
                    if sub_maximum is None:
616
                        sub_maximum = row[1]
617
                    elif sub_maximum < row[1]:
618
                        sub_maximum = row[1]
619
                    sub_counter += 1
620
621
            sub_average = (sub_total / sub_counter) if sub_counter > 0 else None
622
            result_rows_weekly.append((current_datetime_utc, sub_average, sub_maximum))
623
            total += sub_total
624
            counter += sub_counter
625
            if sub_maximum is None:
626
                pass
627
            elif maximum is None:
628
                maximum = sub_maximum
629
            elif maximum < sub_maximum:
630
                maximum = sub_maximum
631
            current_datetime_utc += timedelta(days=7)
632
633
        average = total / counter if counter > 0 else None
634
        return result_rows_weekly, average, maximum
635
636
    elif period_type == "monthly":
637
        result_rows_monthly = list()
638
        # todo: add config.working_day_start_time_local
639
        # todo: add config.minutes_to_count
640
        total = Decimal(0.0)
641
        maximum = None
642
        counter = 0
643
        # calculate the start datetime in utc of the first day in the first month in local
644
        start_datetime_local = start_datetime_utc + timedelta(hours=int(config.utc_offset[1:3]))
645
        current_datetime_utc = \
646
            start_datetime_local.replace(day=1, hour=0) - timedelta(hours=int(config.utc_offset[1:3]))
647
648
        while current_datetime_utc <= end_datetime_utc:
649
            # calculate the next datetime in utc
650 View Code Duplication
            if current_datetime_utc.month == 1:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
651
                temp_day = 28
652
                ny = current_datetime_utc.year
653
                if (ny % 100 != 0 and ny % 4 == 0) or (ny % 100 == 0 and ny % 400 == 0):
654
                    temp_day = 29
655
656
                next_datetime_utc = datetime(year=current_datetime_utc.year,
657
                                             month=current_datetime_utc.month + 1,
658
                                             day=temp_day,
659
                                             hour=current_datetime_utc.hour,
660
                                             minute=current_datetime_utc.minute,
661
                                             second=0,
662
                                             microsecond=0,
663
                                             tzinfo=None)
664
            elif current_datetime_utc.month == 2:
665
                next_datetime_utc = datetime(year=current_datetime_utc.year,
666
                                             month=current_datetime_utc.month + 1,
667
                                             day=31,
668
                                             hour=current_datetime_utc.hour,
669
                                             minute=current_datetime_utc.minute,
670
                                             second=0,
671
                                             microsecond=0,
672
                                             tzinfo=None)
673
            elif current_datetime_utc.month in [3, 5, 8, 10]:
674
                next_datetime_utc = datetime(year=current_datetime_utc.year,
675
                                             month=current_datetime_utc.month + 1,
676
                                             day=30,
677
                                             hour=current_datetime_utc.hour,
678
                                             minute=current_datetime_utc.minute,
679
                                             second=0,
680
                                             microsecond=0,
681
                                             tzinfo=None)
682
            elif current_datetime_utc.month == 7:
683
                next_datetime_utc = datetime(year=current_datetime_utc.year,
684
                                             month=current_datetime_utc.month + 1,
685
                                             day=31,
686
                                             hour=current_datetime_utc.hour,
687
                                             minute=current_datetime_utc.minute,
688
                                             second=0,
689
                                             microsecond=0,
690
                                             tzinfo=None)
691
            elif current_datetime_utc.month in [4, 6, 9, 11]:
692
                next_datetime_utc = datetime(year=current_datetime_utc.year,
693
                                             month=current_datetime_utc.month + 1,
694
                                             day=31,
695
                                             hour=current_datetime_utc.hour,
696
                                             minute=current_datetime_utc.minute,
697
                                             second=0,
698
                                             microsecond=0,
699
                                             tzinfo=None)
700
            elif current_datetime_utc.month == 12:
701
                next_datetime_utc = datetime(year=current_datetime_utc.year + 1,
702
                                             month=1,
703
                                             day=31,
704
                                             hour=current_datetime_utc.hour,
705
                                             minute=current_datetime_utc.minute,
706
                                             second=0,
707
                                             microsecond=0,
708
                                             tzinfo=None)
709
710
            sub_total = Decimal(0.0)
711
            sub_maximum = None
712
            sub_counter = 0
713
            for row in rows_hourly:
714
                if current_datetime_utc <= row[0] < next_datetime_utc:
0 ignored issues
show
introduced by
The variable next_datetime_utc does not seem to be defined for all execution paths.
Loading history...
715
                    sub_total += row[1]
716
                    if sub_maximum is None:
717
                        sub_maximum = row[1]
718
                    elif sub_maximum < row[1]:
719
                        sub_maximum = row[1]
720
                    sub_counter += 1
721
722
            sub_average = (sub_total / sub_counter) if sub_counter > 0 else None
723
            result_rows_monthly.append((current_datetime_utc, sub_average, sub_maximum))
724
            total += sub_total
725
            counter += sub_counter
726
            if sub_maximum is None:
727
                pass
728
            elif maximum is None:
729
                maximum = sub_maximum
730
            elif maximum < sub_maximum:
731
                maximum = sub_maximum
732
            current_datetime_utc = next_datetime_utc
733
734
        average = total / counter if counter > 0 else None
735
        return result_rows_monthly, average, maximum
736
737
    elif period_type == "yearly":
738
        result_rows_yearly = list()
739
        # todo: add config.working_day_start_time_local
740
        # todo: add config.minutes_to_count
741
        total = Decimal(0.0)
742
        maximum = None
743
        counter = 0
744
        # calculate the start datetime in utc of the first day in the first month in local
745
        start_datetime_local = start_datetime_utc + timedelta(hours=int(config.utc_offset[1:3]))
746
        current_datetime_utc = start_datetime_local.replace(month=1, day=1, hour=0) - timedelta(
747
            hours=int(config.utc_offset[1:3]))
748
749
        while current_datetime_utc <= end_datetime_utc:
750
            # calculate the next datetime in utc
751
            # todo: timedelta of year
752
            next_datetime_utc = datetime(year=current_datetime_utc.year + 2,
753
                                         month=1,
754
                                         day=1,
755
                                         hour=current_datetime_utc.hour,
756
                                         minute=current_datetime_utc.minute,
757
                                         second=current_datetime_utc.second,
758
                                         microsecond=current_datetime_utc.microsecond,
759
                                         tzinfo=current_datetime_utc.tzinfo) - timedelta(days=1)
760
            sub_total = Decimal(0.0)
761
            sub_maximum = None
762
            sub_counter = 0
763
            for row in rows_hourly:
764
                if current_datetime_utc <= row[0] < next_datetime_utc:
765
                    sub_total += row[1]
766
                    if sub_maximum is None:
767
                        sub_maximum = row[1]
768
                    elif sub_maximum < row[1]:
769
                        sub_maximum = row[1]
770
                    sub_counter += 1
771
772
            sub_average = (sub_total / sub_counter) if sub_counter > 0 else None
773
            result_rows_yearly.append((current_datetime_utc, sub_average, sub_maximum))
774
            total += sub_total
775
            counter += sub_counter
776
            if sub_maximum is None:
777
                pass
778
            elif maximum is None:
779
                maximum = sub_maximum
780
            elif maximum < sub_maximum:
781
                maximum = sub_maximum
782
            current_datetime_utc = next_datetime_utc
783
784
        average = total / counter if counter > 0 else None
785
        return result_rows_yearly, average, maximum
786
    else:
787
        return list(), None, None
788
789
790
########################################################################################################################
791
# Statistics calculator of hourly data by period
792
#   rows_hourly: list of (start_datetime_utc, actual_value), should belong to one energy_category_id
793
#   start_datetime_utc: start datetime in utc
794
#   end_datetime_utc: end datetime in utc
795
#   period_type: use one of the period types, 'hourly', 'daily', 'weekly', 'monthly' and 'yearly'
796
# Returns: periodically data of values and statistics of mean, median, minimum, maximum, stdev and variance
797
# Note: this procedure doesn't work with multiple energy categories
798
########################################################################################################################
799
def statistics_hourly_data_by_period(rows_hourly, start_datetime_utc, end_datetime_utc, period_type):
800
    # todo: validate parameters
801
    if start_datetime_utc is None or \
802
            end_datetime_utc is None or \
803
            start_datetime_utc >= end_datetime_utc or \
804
            period_type not in ('hourly', 'daily', 'weekly', 'monthly', 'yearly'):
805
        return list(), None, None, None, None, None, None
806
807
    start_datetime_utc = start_datetime_utc.replace(tzinfo=None)
808
    end_datetime_utc = end_datetime_utc.replace(tzinfo=None)
809
810
    if period_type == "hourly":
811
        result_rows_hourly = list()
812
        sample_data = list()
813
        # todo: add config.working_day_start_time_local
814
        # todo: add config.minutes_to_count
815
        counter = 0
816
        mean = None
817
        median = None
818
        minimum = None
819
        maximum = None
820
        stdev = None
821
        variance = None
822
        current_datetime_utc = start_datetime_utc.replace(minute=0, second=0, microsecond=0, tzinfo=None)
823 View Code Duplication
        while current_datetime_utc <= end_datetime_utc:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
824
            sub_total = Decimal(0.0)
825
            for row in rows_hourly:
826
                if current_datetime_utc <= row[0] < current_datetime_utc + \
827
                        timedelta(minutes=config.minutes_to_count):
828
                    sub_total += row[1]
829
830
            result_rows_hourly.append((current_datetime_utc, sub_total))
831
            sample_data.append(sub_total)
832
833
            counter += 1
834
            if minimum is None:
835
                minimum = sub_total
836
            elif minimum > sub_total:
837
                minimum = sub_total
838
839
            if maximum is None:
840
                maximum = sub_total
841
            elif maximum < sub_total:
842
                maximum = sub_total
843
844
            current_datetime_utc += timedelta(minutes=config.minutes_to_count)
845
846
        if len(sample_data) > 1:
847
            mean = statistics.mean(sample_data)
848
            median = statistics.median(sample_data)
849
            stdev = statistics.stdev(sample_data)
850
            variance = statistics.variance(sample_data)
851
852
        return result_rows_hourly, mean, median, minimum, maximum, stdev, variance
853
854
    elif period_type == "daily":
855
        result_rows_daily = list()
856
        sample_data = list()
857
        # todo: add config.working_day_start_time_local
858
        # todo: add config.minutes_to_count
859
        counter = 0
860
        mean = None
861
        median = None
862
        minimum = None
863
        maximum = None
864
        stdev = None
865
        variance = None
866
        # calculate the start datetime in utc of the first day in local
867
        start_datetime_local = start_datetime_utc + timedelta(hours=int(config.utc_offset[1:3]))
868
        current_datetime_utc = start_datetime_local.replace(hour=0) - timedelta(hours=int(config.utc_offset[1:3]))
869 View Code Duplication
        while current_datetime_utc <= end_datetime_utc:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
870
            sub_total = Decimal(0.0)
871
            for row in rows_hourly:
872
                if current_datetime_utc <= row[0] < current_datetime_utc + timedelta(days=1):
873
                    sub_total += row[1]
874
875
            result_rows_daily.append((current_datetime_utc, sub_total))
876
            sample_data.append(sub_total)
877
878
            counter += 1
879
            if minimum is None:
880
                minimum = sub_total
881
            elif minimum > sub_total:
882
                minimum = sub_total
883
884
            if maximum is None:
885
                maximum = sub_total
886
            elif maximum < sub_total:
887
                maximum = sub_total
888
            current_datetime_utc += timedelta(days=1)
889
890
        if len(sample_data) > 1:
891
            mean = statistics.mean(sample_data)
892
            median = statistics.median(sample_data)
893
            stdev = statistics.stdev(sample_data)
894
            variance = statistics.variance(sample_data)
895
896
        return result_rows_daily, mean, median, minimum, maximum, stdev, variance
897
898
    elif period_type == "weekly":
899
        result_rows_weekly = list()
900
        sample_data = list()
901
        # todo: add config.working_day_start_time_local
902
        # todo: add config.minutes_to_count
903
        counter = 0
904
        mean = None
905
        median = None
906
        minimum = None
907
        maximum = None
908
        stdev = None
909
        variance = None
910
        # calculate the start datetime in utc of the monday in the first week in local
911
        start_datetime_local = start_datetime_utc + timedelta(hours=int(config.utc_offset[1:3]))
912
        weekday = start_datetime_local.weekday()
913
        current_datetime_utc = \
914
            start_datetime_local.replace(hour=0) - timedelta(days=weekday, hours=int(config.utc_offset[1:3]))
915 View Code Duplication
        while current_datetime_utc <= end_datetime_utc:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
916
            sub_total = Decimal(0.0)
917
            for row in rows_hourly:
918
                if current_datetime_utc <= row[0] < current_datetime_utc + timedelta(days=7):
919
                    sub_total += row[1]
920
921
            result_rows_weekly.append((current_datetime_utc, sub_total))
922
            sample_data.append(sub_total)
923
924
            counter += 1
925
            if minimum is None:
926
                minimum = sub_total
927
            elif minimum > sub_total:
928
                minimum = sub_total
929
930
            if maximum is None:
931
                maximum = sub_total
932
            elif maximum < sub_total:
933
                maximum = sub_total
934
            current_datetime_utc += timedelta(days=7)
935
936
        if len(sample_data) > 1:
937
            mean = statistics.mean(sample_data)
938
            median = statistics.median(sample_data)
939
            stdev = statistics.stdev(sample_data)
940
            variance = statistics.variance(sample_data)
941
942
        return result_rows_weekly, mean, median, minimum, maximum, stdev, variance
943
944
    elif period_type == "monthly":
945
        result_rows_monthly = list()
946
        sample_data = list()
947
        # todo: add config.working_day_start_time_local
948
        # todo: add config.minutes_to_count
949
        counter = 0
950
        mean = None
951
        median = None
952
        minimum = None
953
        maximum = None
954
        stdev = None
955
        variance = None
956
        # calculate the start datetime in utc of the first day in the first month in local
957
        start_datetime_local = start_datetime_utc + timedelta(hours=int(config.utc_offset[1:3]))
958
        current_datetime_utc = \
959
            start_datetime_local.replace(day=1, hour=0) - timedelta(hours=int(config.utc_offset[1:3]))
960
961
        while current_datetime_utc <= end_datetime_utc:
962
            # calculate the next datetime in utc
963 View Code Duplication
            if current_datetime_utc.month == 1:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
964
                temp_day = 28
965
                ny = current_datetime_utc.year
966
                if (ny % 100 != 0 and ny % 4 == 0) or (ny % 100 == 0 and ny % 400 == 0):
967
                    temp_day = 29
968
969
                next_datetime_utc = datetime(year=current_datetime_utc.year,
970
                                             month=current_datetime_utc.month + 1,
971
                                             day=temp_day,
972
                                             hour=current_datetime_utc.hour,
973
                                             minute=current_datetime_utc.minute,
974
                                             second=0,
975
                                             microsecond=0,
976
                                             tzinfo=None)
977
            elif current_datetime_utc.month == 2:
978
                next_datetime_utc = datetime(year=current_datetime_utc.year,
979
                                             month=current_datetime_utc.month + 1,
980
                                             day=31,
981
                                             hour=current_datetime_utc.hour,
982
                                             minute=current_datetime_utc.minute,
983
                                             second=0,
984
                                             microsecond=0,
985
                                             tzinfo=None)
986
            elif current_datetime_utc.month in [3, 5, 8, 10]:
987
                next_datetime_utc = datetime(year=current_datetime_utc.year,
988
                                             month=current_datetime_utc.month + 1,
989
                                             day=30,
990
                                             hour=current_datetime_utc.hour,
991
                                             minute=current_datetime_utc.minute,
992
                                             second=0,
993
                                             microsecond=0,
994
                                             tzinfo=None)
995
            elif current_datetime_utc.month == 7:
996
                next_datetime_utc = datetime(year=current_datetime_utc.year,
997
                                             month=current_datetime_utc.month + 1,
998
                                             day=31,
999
                                             hour=current_datetime_utc.hour,
1000
                                             minute=current_datetime_utc.minute,
1001
                                             second=0,
1002
                                             microsecond=0,
1003
                                             tzinfo=None)
1004
            elif current_datetime_utc.month in [4, 6, 9, 11]:
1005
                next_datetime_utc = datetime(year=current_datetime_utc.year,
1006
                                             month=current_datetime_utc.month + 1,
1007
                                             day=31,
1008
                                             hour=current_datetime_utc.hour,
1009
                                             minute=current_datetime_utc.minute,
1010
                                             second=0,
1011
                                             microsecond=0,
1012
                                             tzinfo=None)
1013
            elif current_datetime_utc.month == 12:
1014
                next_datetime_utc = datetime(year=current_datetime_utc.year + 1,
1015
                                             month=1,
1016
                                             day=31,
1017
                                             hour=current_datetime_utc.hour,
1018
                                             minute=current_datetime_utc.minute,
1019
                                             second=0,
1020
                                             microsecond=0,
1021
                                             tzinfo=None)
1022
1023
            sub_total = Decimal(0.0)
1024
            for row in rows_hourly:
1025
                if current_datetime_utc <= row[0] < next_datetime_utc:
0 ignored issues
show
introduced by
The variable next_datetime_utc does not seem to be defined for all execution paths.
Loading history...
1026
                    sub_total += row[1]
1027
1028
            result_rows_monthly.append((current_datetime_utc, sub_total))
1029
            sample_data.append(sub_total)
1030
1031
            counter += 1
1032
            if minimum is None:
1033
                minimum = sub_total
1034
            elif minimum > sub_total:
1035
                minimum = sub_total
1036
1037
            if maximum is None:
1038
                maximum = sub_total
1039
            elif maximum < sub_total:
1040
                maximum = sub_total
1041
            current_datetime_utc = next_datetime_utc
1042
1043
        if len(sample_data) > 1:
1044
            mean = statistics.mean(sample_data)
1045
            median = statistics.median(sample_data)
1046
            stdev = statistics.stdev(sample_data)
1047
            variance = statistics.variance(sample_data)
1048
1049
        return result_rows_monthly, mean, median, minimum, maximum, stdev, variance
1050
1051
    elif period_type == "yearly":
1052
        result_rows_yearly = list()
1053
        sample_data = list()
1054
        # todo: add config.working_day_start_time_local
1055
        # todo: add config.minutes_to_count
1056
        mean = None
1057
        median = None
1058
        minimum = None
1059
        maximum = None
1060
        stdev = None
1061
        variance = None
1062
        # calculate the start datetime in utc of the first day in the first month in local
1063
        start_datetime_local = start_datetime_utc + timedelta(hours=int(config.utc_offset[1:3]))
1064
        current_datetime_utc = start_datetime_local.replace(month=1, day=1, hour=0) - timedelta(
1065
            hours=int(config.utc_offset[1:3]))
1066
1067
        while current_datetime_utc <= end_datetime_utc:
1068
            # calculate the next datetime in utc
1069
            # todo: timedelta of year
1070
            next_datetime_utc = datetime(year=current_datetime_utc.year + 2,
1071
                                         month=1,
1072
                                         day=1,
1073
                                         hour=current_datetime_utc.hour,
1074
                                         minute=current_datetime_utc.minute,
1075
                                         second=current_datetime_utc.second,
1076
                                         microsecond=current_datetime_utc.microsecond,
1077
                                         tzinfo=current_datetime_utc.tzinfo) - timedelta(days=1)
1078
            sub_total = Decimal(0.0)
1079
            for row in rows_hourly:
1080
                if current_datetime_utc <= row[0] < next_datetime_utc:
1081
                    sub_total += row[1]
1082
1083
            result_rows_yearly.append((current_datetime_utc, sub_total))
1084
            sample_data.append(sub_total)
1085
1086
            if minimum is None:
1087
                minimum = sub_total
1088
            elif minimum > sub_total:
1089
                minimum = sub_total
1090
            if maximum is None:
1091
                maximum = sub_total
1092
            elif maximum < sub_total:
1093
                maximum = sub_total
1094
1095
            current_datetime_utc = next_datetime_utc
1096
1097
        if len(sample_data) > 1:
1098
            mean = statistics.mean(sample_data)
1099
            median = statistics.median(sample_data)
1100
            stdev = statistics.stdev(sample_data)
1101
            variance = statistics.variance(sample_data)
1102
1103
        return result_rows_yearly, mean, median, minimum, maximum, stdev, variance
1104
1105
    else:
1106
        return list(), None, None, None, None, None, None
1107
1108
1109
def get_translation(language):
1110
    if language is None or not isinstance(language, str) or len(language) == 0:
1111
        return gettext.translation('myems', './i18n/', languages=['en'])
1112
1113
    if language not in ['zh_CN', 'en', 'de', 'fr', 'es', 'ru', 'ar', 'vi', 'th', 'tr', 'ms', 'id', 'zh_TW', 'pt']:
1114
        return gettext.translation('myems', './i18n/', languages=['en'])
1115
    else:
1116
        language_list = [language]
1117
        return gettext.translation('myems', './i18n/', languages=language_list)
1118
1119
1120
def int16_to_hhmm(actual_value):
1121
    """Convert int16 to time in HH:mm"""
1122
    hh = int(actual_value / 256)
1123
    if hh < 10:
1124
        hh = '0' + str(hh)
1125
    elif hh < 24:
1126
        hh = str(hh)
1127
    else:
1128
        return None
1129
    mm = actual_value % 256
1130
    if mm < 10:
1131
        mm = '0' + str(mm)
1132
    elif mm < 60:
1133
        mm = str(mm)
1134
    else:
1135
        return None
1136
    return hh + ':' + mm
1137
1138
1139
def round2(actual_value, precision):
1140
    if actual_value is not None:
1141
        try:
1142
            result = round(actual_value, precision)
1143
        except (TypeError, NameError, SyntaxError):
1144
            return "-"
1145
        except Exception:
1146
            return "-"
1147
        return result
1148
    else:
1149
        return "-"
1150