|
14 | 14 | from frugy.fru_registry import FruRecordType, rec_register |
15 | 15 | from datetime import datetime, timedelta |
16 | 16 | import logging |
17 | | - |
| 17 | +import frugy |
18 | 18 |
|
19 | 19 | def ipmi_area(cls): |
20 | 20 | rec_register(cls, FruRecordType.ipmi_area) |
@@ -77,23 +77,61 @@ class BoardInfo(FruAreaSized): |
77 | 77 | ('custom_info_fields', CustomStringArray), |
78 | 78 | ] |
79 | 79 |
|
| 80 | + # With a Y2K27 rollover baked into the specification, we have to deal with |
| 81 | + # - 24bit overflow when converting yaml to bin |
| 82 | + # - uncertainty of original 25th bit when converting bin to yaml |
| 83 | + # see also: https://github.com/MicroTCA-Tech-Lab/frugy/issues/11 |
80 | 84 | _time_ref = datetime(1996, 1, 1) |
| 85 | + _time_rollover_limit = 2**24 |
| 86 | + |
| 87 | + def _timestamp_to_minutes(self, timestamp): |
| 88 | + td = timestamp - self._time_ref |
| 89 | + return td.seconds // 60 + td.days * (60*24) |
| 90 | + |
| 91 | + def _timestamp_from_minutes(self, minutes): |
| 92 | + return BoardInfo._time_ref + timedelta(minutes=minutes) |
| 93 | + |
| 94 | + def _handle_y2k27_rollover_yaml2bin(self, minutes): |
| 95 | + # Truncate the timestamp to 24 bits |
| 96 | + # If the remainder is nonzero, log a warning. |
| 97 | + minutes_truncated = minutes % BoardInfo._time_rollover_limit |
| 98 | + if minutes != minutes_truncated: |
| 99 | + min_str = self._timestamp_from_minutes(minutes).isoformat() |
| 100 | + min_trunc_str = self._timestamp_from_minutes(minutes_truncated).isoformat() |
| 101 | + logging.warning(f'Timestamp {min_str} truncated; may be interpreted by FRU parsers as {min_trunc_str}') |
| 102 | + return minutes_truncated |
| 103 | + |
| 104 | + def _handle_y2k27_rollover_bin2yaml(self, minutes, now): |
| 105 | + # Apply heuristic to possibly truncated timestamp: |
| 106 | + # If it is "too old" relative to the system time, add rollover timedelta and log a warning. |
| 107 | + # We draw the line at 31 years into the past (not the full 2**24 minutes). |
| 108 | + # This will allow a margin of a couple months in case the system time is incorrect |
| 109 | + # or the board is dated a bit into the future. |
| 110 | + if now - self._timestamp_from_minutes(minutes) > timedelta(days=31*365): |
| 111 | + minutes_ext = minutes + self._time_rollover_limit |
| 112 | + min_str = self._timestamp_from_minutes(minutes).isoformat() |
| 113 | + min_ext_str = self._timestamp_from_minutes(minutes_ext).isoformat() |
| 114 | + warn_str = f'BoardInfo.mfg_date_time: possible Y2K27 rollover detected; timestamp bumped to {min_ext_str} - original timestamp was {min_str}' |
| 115 | + logging.warning(warn_str) |
| 116 | + frugy.fru.import_log(warn_str) |
| 117 | + return minutes_ext |
| 118 | + return minutes |
81 | 119 |
|
82 | 120 | def _set_mfg_date_time(self, timestamp): |
83 | 121 | if timestamp is not None: |
84 | 122 | if type(timestamp) == str: |
85 | 123 | timestamp = datetime.fromisoformat(timestamp) |
86 | | - td = timestamp - BoardInfo._time_ref |
87 | | - minutes = td.seconds // 60 + td.days * (60*24) |
| 124 | + minutes = self._timestamp_to_minutes(timestamp) |
| 125 | + minutes = self._handle_y2k27_rollover_yaml2bin(minutes) |
88 | 126 | self._set('mfg_date_time', minutes) |
89 | 127 | else: |
90 | 128 | self._set('mfg_date_time', 0) |
91 | 129 |
|
92 | 130 | def _get_mfg_date_time(self): |
93 | 131 | minutes = self._get('mfg_date_time') |
94 | 132 | if minutes != 0: |
95 | | - timestamp = BoardInfo._time_ref + timedelta(minutes=minutes) |
96 | | - return timestamp |
| 133 | + minutes = self._handle_y2k27_rollover_bin2yaml(minutes, datetime.now()) |
| 134 | + return self._timestamp_from_minutes(minutes) |
97 | 135 | else: |
98 | 136 | return None |
99 | 137 |
|
|
0 commit comments