cengal.time_management.relative_time.approximate_representation.versions.v_0.approximate_representation

  1#!/usr/bin/env python
  2# coding=utf-8
  3
  4# Copyright © 2012-2024 ButenkoMS. All rights reserved. Contacts: <gtalk@butenkoms.space>
  5# 
  6# Licensed under the Apache License, Version 2.0 (the "License");
  7# you may not use this file except in compliance with the License.
  8# You may obtain a copy of the License at
  9# 
 10#     http://www.apache.org/licenses/LICENSE-2.0
 11# 
 12# Unless required by applicable law or agreed to in writing, software
 13# distributed under the License is distributed on an "AS IS" BASIS,
 14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 15# See the License for the specific language governing permissions and
 16# limitations under the License.
 17
 18from typing import Union, Set, Tuple, Dict
 19from math import modf
 20from enum import Enum
 21from copy import copy
 22from cengal.data_manipulation.conversion.mapping import inverse_mapping
 23from cengal.text_processing.optional_formatter.versions.v_0 import OptionalFormatter
 24from ....constants import *
 25
 26"""
 27Module Docstring
 28Docstrings: http://www.python.org/dev/peps/pep-0257/
 29"""
 30
 31__author__ = "ButenkoMS <gtalk@butenkoms.space>"
 32__copyright__ = "Copyright © 2012-2024 ButenkoMS. All rights reserved. Contacts: <gtalk@butenkoms.space>"
 33__credits__ = ["ButenkoMS <gtalk@butenkoms.space>", ]
 34__license__ = "Apache License, Version 2.0"
 35__version__ = "4.4.1"
 36__maintainer__ = "ButenkoMS <gtalk@butenkoms.space>"
 37__email__ = "gtalk@butenkoms.space"
 38# __status__ = "Prototype"
 39__status__ = "Development"
 40# __status__ = "Production"
 41
 42
 43SECONDS_PER_YEAR = SECONDS_PER_DAY * 365
 44SECONDS_PER_MONTH = round(SECONDS_PER_YEAR / 12)
 45SECONDS_PER_DECADE = SECONDS_PER_YEAR * 10
 46SECONDS_PER_CENTURY = SECONDS_PER_YEAR * 100
 47SECONDS_PER_MILLENIA = SECONDS_PER_YEAR * 1000
 48
 49
 50class TimeAttributes(Enum):
 51    microseconds = 0
 52    milliseconds = 1
 53    seconds = 2
 54    minutes = 3
 55    hours = 4
 56    days = 5
 57    weeks = 6
 58    months = 7
 59    years = 8
 60    decades = 9
 61    centuries = 10
 62    millennia = 11
 63
 64
 65ATTRIBUTES_ASCENDING = sorted(TimeAttributes, key=lambda item: item.value, reverse=False)
 66ATTRIBUTES_DESCENDING = sorted(TimeAttributes, key=lambda item: item.value, reverse=True)
 67
 68
 69DIVIDER_PER_ATTRIBUTE = {
 70    TimeAttributes.millennia: SECONDS_PER_MILLENIA,
 71    TimeAttributes.centuries: SECONDS_PER_CENTURY,
 72    TimeAttributes.decades: SECONDS_PER_DECADE,
 73    TimeAttributes.years: SECONDS_PER_YEAR,
 74    TimeAttributes.months: SECONDS_PER_MONTH,
 75    TimeAttributes.weeks: SECONDS_PER_WEEK,
 76    TimeAttributes.days: SECONDS_PER_DAY,
 77    TimeAttributes.hours: SECONDS_PER_HOUR,
 78    TimeAttributes.minutes: SECONDS_PER_MINUTE
 79}
 80
 81ATTRIBUTE_PER_DIVIDER = inverse_mapping(DIVIDER_PER_ATTRIBUTE)
 82
 83DIV_ATTRIBUTES = set(DIVIDER_PER_ATTRIBUTE)
 84DIV_ATTRIBUTES_ASCENDING = sorted(DIV_ATTRIBUTES, key=lambda item: item.value, reverse=False)
 85DIV_ATTRIBUTES_DESCENDING = sorted(DIV_ATTRIBUTES, key=lambda item: item.value, reverse=True)
 86
 87
 88MULTIPLIER_PER_ATTRIBUTE = {
 89    TimeAttributes.milliseconds: 1 / SECONDS_PER_MILLISECOND,
 90    TimeAttributes.microseconds: 1 / SECONDS_PER_MICROSECOND
 91}
 92
 93ATTRIBUTE_PER_MULTIPLIER = inverse_mapping(MULTIPLIER_PER_ATTRIBUTE)
 94
 95MUL_ATTRIBUTES = set(MULTIPLIER_PER_ATTRIBUTE)
 96MUL_ATTRIBUTES_ASCENDING = sorted(MUL_ATTRIBUTES, key=lambda item: item.value, reverse=False)
 97MUL_ATTRIBUTES_DESCENDING = sorted(MUL_ATTRIBUTES, key=lambda item: item.value, reverse=True)
 98
 99
100DEFAULT_ATTRIBUTES = {
101    TimeAttributes.years
102    , TimeAttributes.months
103    , TimeAttributes.days
104    , TimeAttributes.hours
105    , TimeAttributes.minutes
106    , TimeAttributes.seconds
107    , TimeAttributes.microseconds
108}
109
110DEFAULT_ATTRIBUTES_ASCENDING = sorted(DEFAULT_ATTRIBUTES, key=lambda item: item.value, reverse=False)
111DEFAULT_ATTRIBUTES_DESCENDING = sorted(DEFAULT_ATTRIBUTES, key=lambda item: item.value, reverse=True)
112
113
114DEFAULT_FORMATTER = OptionalFormatter(tuple(DEFAULT_ATTRIBUTES_DESCENDING), {
115    TimeAttributes.years: ('',  'Y', '{}', '', '-'),
116    TimeAttributes.months: ('', 'M',  '{}', '', '-'),
117    TimeAttributes.days: ('', 'D', '{}', '', ' '),
118    TimeAttributes.hours: ('', 'h', '{}', '', ''),
119    TimeAttributes.minutes: (':', 'm', '{}', '', ''),
120    TimeAttributes.seconds: (':', 's', '{}', '', ''),
121    TimeAttributes.microseconds: ('.', 'μs', '{0:0>6}', '', '')
122})
123
124FULL_FORMATTER = OptionalFormatter(tuple(ATTRIBUTES_DESCENDING), {
125    TimeAttributes.millennia: ('', '', 'Millennia: {}', '', ', '),
126    TimeAttributes.centuries: ('', '', 'Centuries: {}', '', ', '),
127    TimeAttributes.decades: ('', '', 'Decades: {}', '', ', '),
128    TimeAttributes.years: ('', '', 'Years: {}', '', ', '),
129    TimeAttributes.months: ('', '', 'Months: {}', '', ', '),
130    TimeAttributes.weeks: ('', '', 'Weeks: {}', '', ', '),
131    TimeAttributes.days: ('', '', 'Days: {}', '', ', '),
132    TimeAttributes.hours: ('', '', 'Hours: {}', '', ', '),
133    TimeAttributes.minutes: ('', '', 'Minutes: {}', '', ', '),
134    TimeAttributes.seconds: ('', '', 'Seconds: {}', '', ', '),
135    TimeAttributes.milliseconds: ('', '', 'Milliseconds: {0:0>3}', '', ', '),
136    TimeAttributes.microseconds: ('', '', 'Microseconds: {0:0>6}', '', ', '),
137})
138
139
140class ApproximateTimeRepresentation:
141    def __init__(self, seconds: Union[int, float], attributes: Union[Set[TimeAttributes], None]=None, crop: bool=False):
142        self._initial__seconds = seconds
143        self._initial__attributes = attributes or DEFAULT_ATTRIBUTES
144        self._initial__crop = crop
145
146        self.values = dict()
147
148        rest = self._initial__seconds
149        for attribute in DIV_ATTRIBUTES_DESCENDING:
150            self.values[attribute], rest = self.compute_attribute(rest, attribute)
151
152        rest, seconds = modf(rest)
153        seconds = int(seconds)
154        self.values[TimeAttributes.seconds] = seconds
155
156        for attribute in MUL_ATTRIBUTES_DESCENDING:
157            self.values[attribute], rest = self.compute_fractional_attribute(rest, attribute)
158
159        self.cropped_values = copy(self.values)
160        self._normalize()
161        if self._initial__crop:
162            self._crop()
163
164    def compute_attribute(self, seconds: float,
165                          attribute: TimeAttributes) -> Tuple[float, float]:
166        if attribute in self._initial__attributes:
167            result, rest = divmod(seconds, DIVIDER_PER_ATTRIBUTE[attribute])
168            result = int(result)
169            return result, rest
170        else:
171            return 0, seconds
172
173    def compute_fractional_attribute(self, seconds: float,
174                                     attribute: TimeAttributes) -> Tuple[float, float]:
175        if attribute in self._initial__attributes:
176            multiplier = MULTIPLIER_PER_ATTRIBUTE[attribute]
177            rest, integer_value = modf(seconds * multiplier)
178            integer_value = int(integer_value)
179            rest /= multiplier
180            return integer_value, rest
181        else:
182            return 0, seconds
183
184    def _normalize(self):
185        for attribute in TimeAttributes:
186            if attribute not in self._initial__attributes:
187                if attribute in self.cropped_values:
188                    del self.cropped_values[attribute]
189
190    def _crop(self):
191        descending_stopped = False
192        ascending_stopped = False
193
194        for attribute in ATTRIBUTES_DESCENDING:
195            descending_stopped = self._crop__check_attribute(self.values[attribute], attribute, descending_stopped)
196
197        for attribute in ATTRIBUTES_ASCENDING:
198            ascending_stopped = self._crop__check_attribute(self.values[attribute], attribute, ascending_stopped)
199
200    def _crop__check_attribute(self, value: Union[int, float], attribute: TimeAttributes, stopped: bool) -> bool:
201        if stopped or (0 != value):
202            return True
203        else:
204            if attribute in self.cropped_values:
205                del self.cropped_values[attribute]
206            return False
207
208    def format(self, formatter: Union[OptionalFormatter, None]=None) -> str:
209        formatter = formatter or DEFAULT_FORMATTER
210        args = tuple()
211        return formatter(self._construct_formatter_dict(self.values))
212
213    def format_cropped(self, formatter: Union[OptionalFormatter, None]=None) -> str:
214        formatter = formatter or DEFAULT_FORMATTER
215        return formatter(self._construct_formatter_dict(self.cropped_values))
216
217    def __str__(self):
218        formatter = FULL_FORMATTER
219        return '{class_name}{{{data}}}'.format(class_name=self.__class__.__name__,
220                                               data=formatter(self._construct_formatter_dict(self.values)))
221
222    @staticmethod
223    def _construct_formatter_dict(values: Dict[TimeAttributes, Union[int, float]]):
224        result = dict()
225        for item, value in values.items():
226            result[item] = ((value,), dict())
227        return result
SECONDS_PER_YEAR = 525600
SECONDS_PER_MONTH = 43800
SECONDS_PER_DECADE = 5256000
SECONDS_PER_CENTURY = 52560000
SECONDS_PER_MILLENIA = 525600000
class TimeAttributes(enum.Enum):
51class TimeAttributes(Enum):
52    microseconds = 0
53    milliseconds = 1
54    seconds = 2
55    minutes = 3
56    hours = 4
57    days = 5
58    weeks = 6
59    months = 7
60    years = 8
61    decades = 9
62    centuries = 10
63    millennia = 11

An enumeration.

microseconds = <TimeAttributes.microseconds: 0>
milliseconds = <TimeAttributes.milliseconds: 1>
seconds = <TimeAttributes.seconds: 2>
minutes = <TimeAttributes.minutes: 3>
hours = <TimeAttributes.hours: 4>
days = <TimeAttributes.days: 5>
weeks = <TimeAttributes.weeks: 6>
months = <TimeAttributes.months: 7>
years = <TimeAttributes.years: 8>
decades = <TimeAttributes.decades: 9>
centuries = <TimeAttributes.centuries: 10>
millennia = <TimeAttributes.millennia: 11>
Inherited Members
enum.Enum
name
value
DIVIDER_PER_ATTRIBUTE = {<TimeAttributes.millennia: 11>: 525600000, <TimeAttributes.centuries: 10>: 52560000, <TimeAttributes.decades: 9>: 5256000, <TimeAttributes.years: 8>: 525600, <TimeAttributes.months: 7>: 43800, <TimeAttributes.weeks: 6>: 10080, <TimeAttributes.days: 5>: 1440, <TimeAttributes.hours: 4>: 3600, <TimeAttributes.minutes: 3>: 60}
ATTRIBUTE_PER_DIVIDER = {525600000: <TimeAttributes.millennia: 11>, 52560000: <TimeAttributes.centuries: 10>, 5256000: <TimeAttributes.decades: 9>, 525600: <TimeAttributes.years: 8>, 43800: <TimeAttributes.months: 7>, 10080: <TimeAttributes.weeks: 6>, 1440: <TimeAttributes.days: 5>, 3600: <TimeAttributes.hours: 4>, 60: <TimeAttributes.minutes: 3>}
MULTIPLIER_PER_ATTRIBUTE = {<TimeAttributes.milliseconds: 1>: 1000.0, <TimeAttributes.microseconds: 0>: 1000000.0}
ATTRIBUTE_PER_MULTIPLIER = {1000.0: <TimeAttributes.milliseconds: 1>, 1000000.0: <TimeAttributes.microseconds: 0>}
MUL_ATTRIBUTES_ASCENDING = [<TimeAttributes.microseconds: 0>, <TimeAttributes.milliseconds: 1>]
MUL_ATTRIBUTES_DESCENDING = [<TimeAttributes.milliseconds: 1>, <TimeAttributes.microseconds: 0>]
DEFAULT_FORMATTER = <cengal.text_processing.optional_formatter.versions.v_0.optional_formatter.OptionalFormatter object>
FULL_FORMATTER = <cengal.text_processing.optional_formatter.versions.v_0.optional_formatter.OptionalFormatter object>
class ApproximateTimeRepresentation:
141class ApproximateTimeRepresentation:
142    def __init__(self, seconds: Union[int, float], attributes: Union[Set[TimeAttributes], None]=None, crop: bool=False):
143        self._initial__seconds = seconds
144        self._initial__attributes = attributes or DEFAULT_ATTRIBUTES
145        self._initial__crop = crop
146
147        self.values = dict()
148
149        rest = self._initial__seconds
150        for attribute in DIV_ATTRIBUTES_DESCENDING:
151            self.values[attribute], rest = self.compute_attribute(rest, attribute)
152
153        rest, seconds = modf(rest)
154        seconds = int(seconds)
155        self.values[TimeAttributes.seconds] = seconds
156
157        for attribute in MUL_ATTRIBUTES_DESCENDING:
158            self.values[attribute], rest = self.compute_fractional_attribute(rest, attribute)
159
160        self.cropped_values = copy(self.values)
161        self._normalize()
162        if self._initial__crop:
163            self._crop()
164
165    def compute_attribute(self, seconds: float,
166                          attribute: TimeAttributes) -> Tuple[float, float]:
167        if attribute in self._initial__attributes:
168            result, rest = divmod(seconds, DIVIDER_PER_ATTRIBUTE[attribute])
169            result = int(result)
170            return result, rest
171        else:
172            return 0, seconds
173
174    def compute_fractional_attribute(self, seconds: float,
175                                     attribute: TimeAttributes) -> Tuple[float, float]:
176        if attribute in self._initial__attributes:
177            multiplier = MULTIPLIER_PER_ATTRIBUTE[attribute]
178            rest, integer_value = modf(seconds * multiplier)
179            integer_value = int(integer_value)
180            rest /= multiplier
181            return integer_value, rest
182        else:
183            return 0, seconds
184
185    def _normalize(self):
186        for attribute in TimeAttributes:
187            if attribute not in self._initial__attributes:
188                if attribute in self.cropped_values:
189                    del self.cropped_values[attribute]
190
191    def _crop(self):
192        descending_stopped = False
193        ascending_stopped = False
194
195        for attribute in ATTRIBUTES_DESCENDING:
196            descending_stopped = self._crop__check_attribute(self.values[attribute], attribute, descending_stopped)
197
198        for attribute in ATTRIBUTES_ASCENDING:
199            ascending_stopped = self._crop__check_attribute(self.values[attribute], attribute, ascending_stopped)
200
201    def _crop__check_attribute(self, value: Union[int, float], attribute: TimeAttributes, stopped: bool) -> bool:
202        if stopped or (0 != value):
203            return True
204        else:
205            if attribute in self.cropped_values:
206                del self.cropped_values[attribute]
207            return False
208
209    def format(self, formatter: Union[OptionalFormatter, None]=None) -> str:
210        formatter = formatter or DEFAULT_FORMATTER
211        args = tuple()
212        return formatter(self._construct_formatter_dict(self.values))
213
214    def format_cropped(self, formatter: Union[OptionalFormatter, None]=None) -> str:
215        formatter = formatter or DEFAULT_FORMATTER
216        return formatter(self._construct_formatter_dict(self.cropped_values))
217
218    def __str__(self):
219        formatter = FULL_FORMATTER
220        return '{class_name}{{{data}}}'.format(class_name=self.__class__.__name__,
221                                               data=formatter(self._construct_formatter_dict(self.values)))
222
223    @staticmethod
224    def _construct_formatter_dict(values: Dict[TimeAttributes, Union[int, float]]):
225        result = dict()
226        for item, value in values.items():
227            result[item] = ((value,), dict())
228        return result
ApproximateTimeRepresentation( seconds: typing.Union[int, float], attributes: typing.Union[typing.Set[TimeAttributes], NoneType] = None, crop: bool = False)
142    def __init__(self, seconds: Union[int, float], attributes: Union[Set[TimeAttributes], None]=None, crop: bool=False):
143        self._initial__seconds = seconds
144        self._initial__attributes = attributes or DEFAULT_ATTRIBUTES
145        self._initial__crop = crop
146
147        self.values = dict()
148
149        rest = self._initial__seconds
150        for attribute in DIV_ATTRIBUTES_DESCENDING:
151            self.values[attribute], rest = self.compute_attribute(rest, attribute)
152
153        rest, seconds = modf(rest)
154        seconds = int(seconds)
155        self.values[TimeAttributes.seconds] = seconds
156
157        for attribute in MUL_ATTRIBUTES_DESCENDING:
158            self.values[attribute], rest = self.compute_fractional_attribute(rest, attribute)
159
160        self.cropped_values = copy(self.values)
161        self._normalize()
162        if self._initial__crop:
163            self._crop()
values
cropped_values
def compute_attribute( self, seconds: float, attribute: TimeAttributes) -> Tuple[float, float]:
165    def compute_attribute(self, seconds: float,
166                          attribute: TimeAttributes) -> Tuple[float, float]:
167        if attribute in self._initial__attributes:
168            result, rest = divmod(seconds, DIVIDER_PER_ATTRIBUTE[attribute])
169            result = int(result)
170            return result, rest
171        else:
172            return 0, seconds
def compute_fractional_attribute( self, seconds: float, attribute: TimeAttributes) -> Tuple[float, float]:
174    def compute_fractional_attribute(self, seconds: float,
175                                     attribute: TimeAttributes) -> Tuple[float, float]:
176        if attribute in self._initial__attributes:
177            multiplier = MULTIPLIER_PER_ATTRIBUTE[attribute]
178            rest, integer_value = modf(seconds * multiplier)
179            integer_value = int(integer_value)
180            rest /= multiplier
181            return integer_value, rest
182        else:
183            return 0, seconds
def format( self, formatter: typing.Union[cengal.text_processing.optional_formatter.versions.v_0.optional_formatter.OptionalFormatter, NoneType] = None) -> str:
209    def format(self, formatter: Union[OptionalFormatter, None]=None) -> str:
210        formatter = formatter or DEFAULT_FORMATTER
211        args = tuple()
212        return formatter(self._construct_formatter_dict(self.values))
def format_cropped( self, formatter: typing.Union[cengal.text_processing.optional_formatter.versions.v_0.optional_formatter.OptionalFormatter, NoneType] = None) -> str:
214    def format_cropped(self, formatter: Union[OptionalFormatter, None]=None) -> str:
215        formatter = formatter or DEFAULT_FORMATTER
216        return formatter(self._construct_formatter_dict(self.cropped_values))