master
html 205 lines 6.48 KB
Raw
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <meta http-equiv="X-UA-Compatible" content="IE=edge">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <title>Duration Converter</title>
8 <style>
9 table {
10 width: 50%;
11 border-collapse: collapse;
12 margin-top: 20px;
13 }
14 table, th, td {
15 border: 1px solid black;
16 }
17 th, td {
18 padding: 10px;
19 text-align: center;
20 }
21 .error {
22 color: red;
23 margin-top: 10px;
24 }
25 </style>
26 </head>
27 <body>
28 <h1>Duration Converter</h1>
29 <input type="text" id="durationInput" placeholder="Enter duration (e.g., 10d-12h)">
30 <div id="errorMessage" class="error"></div>
31
32 <table id="resultTable">
33 <thead>
34 <tr>
35 <th>Unit</th>
36 <th>Value</th>
37 <th>Formatted</th>
38 <th>Check</th>
39 </tr>
40 </thead>
41 <tbody>
42 </tbody>
43 </table>
44
45 <script>
46 const NSEC_PER_USEC = 1000;
47 const USEC_PER_MS = 1000;
48 const NSEC_PER_SEC = 1000000000;
49 const NSEC_PER_MS = USEC_PER_MS * NSEC_PER_USEC;
50 const NSEC_PER_MIN = NSEC_PER_SEC * 60;
51 const NSEC_PER_HOUR = NSEC_PER_MIN * 60;
52 const NSEC_PER_DAY = NSEC_PER_HOUR * 24;
53 const NSEC_PER_WEEK = NSEC_PER_DAY * 7;
54 const NSEC_PER_YEAR = NSEC_PER_DAY * 365;
55 const NSEC_PER_MONTH = NSEC_PER_DAY * 30;
56 const NSEC_PER_QUARTER = NSEC_PER_MONTH * 3;
57
58 const units = [
59 { unit: "ns", formatter: true, multiplier: 1 },
60 { unit: "us", formatter: true, multiplier: NSEC_PER_USEC },
61 { unit: "ms", formatter: true, multiplier: NSEC_PER_MS },
62 { unit: "s", formatter: true, multiplier: NSEC_PER_SEC },
63 { unit: "m", formatter: true, multiplier: NSEC_PER_MIN },
64 { unit: "min", formatter: false, multiplier: NSEC_PER_MIN },
65 { unit: "h", formatter: true, multiplier: NSEC_PER_HOUR },
66 { unit: "d", formatter: true, multiplier: NSEC_PER_DAY },
67 { unit: "w", formatter: false, multiplier: NSEC_PER_WEEK },
68 { unit: "wk", formatter: false, multiplier: NSEC_PER_WEEK },
69 { unit: "mo", formatter: true, multiplier: NSEC_PER_MONTH },
70 { unit: "M", formatter: false, multiplier: NSEC_PER_MONTH },
71 { unit: "q", formatter: false, multiplier: NSEC_PER_QUARTER },
72 { unit: "y", formatter: true, multiplier: NSEC_PER_YEAR },
73 { unit: "Y", formatter: false, multiplier: NSEC_PER_YEAR },
74 { unit: "a", formatter: false, multiplier: NSEC_PER_YEAR }
75 ];
76
77 function durationFindUnit(unit) {
78 if (!unit) return units[0];
79 return units.find(u => u.unit === unit) || null;
80 }
81
82 function roundToResolution(value, resolution) {
83 if (value > 0) return Math.floor((value + (resolution - 1) / 2) / resolution);
84 if (value < 0) return Math.ceil((value - (resolution - 1) / 2) / resolution);
85 return 0;
86 }
87
88 function parseDouble(str) {
89 str = str.trim();
90 const match = str.match(/^[-+]?\d*\.?\d+/);
91 if (match) {
92 const number = parseFloat(match[0]);
93 const remainingStr = str.slice(match[0].length).trim();
94 return { number, remainingStr };
95 }
96 return { number: null, remainingStr: str };
97 }
98
99 function durationParse(duration, unit) {
100 if (!duration || !unit) return false;
101
102 let s = duration.trim();
103 let nsec = 0;
104 let isNegative = false;
105
106 // Handle leading negative sign
107 if (s.startsWith("-")) {
108 isNegative = true;
109 s = s.slice(1).trim();
110 }
111
112 while (s.length > 0) {
113 s = s.trim();
114
115 if (s.startsWith("never") || s.startsWith("off"))
116 return 0;
117
118 const { number, remainingStr } = parseDouble(s);
119 if (number === null) return false;
120
121 s = remainingStr;
122
123 const match = s.match(/^([a-zA-Z]*)/);
124 let currentUnit = unit;
125 if (match && match[0].length > 0) {
126 currentUnit = match[0];
127 s = s.slice(match[0].length).trim();
128 }
129
130 const du = durationFindUnit(currentUnit);
131 if (!du) return false;
132
133 nsec += number * du.multiplier;
134 }
135
136 const unitMultiplier = durationFindUnit(unit).multiplier;
137 nsec = roundToResolution(nsec, unitMultiplier);
138
139 return isNegative ? -nsec : nsec;
140 }
141
142 function durationSnprintf(value, unit) {
143 if (value === 0) return "off";
144
145 const duMin = durationFindUnit(unit);
146 let nsec = Math.abs(value) * duMin.multiplier;
147
148 const isNegative = value < 0;
149 let result = isNegative ? "-" : "";
150
151 for (let i = units.length - 1; i >= 0 && nsec !== 0; i--) {
152 const du = units[i];
153 if (!du.formatter && du !== duMin) continue;
154
155 const multiplier = du.multiplier;
156 const rounded = (du === duMin) ? roundToResolution(nsec, multiplier) * multiplier : nsec;
157 let unitCount = Math.floor(rounded / multiplier);
158
159 if (unitCount !== 0) {
160 result += `${unitCount}${du.unit}`;
161 nsec -= unitCount * multiplier;
162 }
163
164 if (du === duMin) break;
165 }
166
167 return result || "off";
168 }
169
170 function updateTable() {
171 const duration = document.getElementById("durationInput").value;
172 const tableBody = document.getElementById("resultTable").querySelector("tbody");
173 const errorMessage = document.getElementById("errorMessage");
174 tableBody.innerHTML = "";
175 errorMessage.textContent = "";
176
177 units.forEach(unit => {
178 let value = durationParse(duration, unit.unit);
179 let formatted;
180 let check;
181 if(value === false) {
182 value = "-";
183 formatted = "";
184 check = "parsing error";
185 }
186 else {
187 formatted = durationSnprintf(value, unit.unit);
188 const parsedValue = durationParse(formatted, unit.unit);
189 check = (parsedValue === value) ? "ok" : `re-parsing error (${parsedValue})`;
190 }
191
192 const row = `<tr>
193 <td>${unit.unit}</td>
194 <td>${value}</td>
195 <td>${formatted}</td>
196 <td>${check}</td>
197 </tr>`;
198 tableBody.innerHTML += row;
199 });
200 }
201
202 document.getElementById("durationInput").addEventListener("input", updateTable);
203 </script>
204 </body>
205 </html>