lib.typ (7464B)
1 // Helper function to format timeframe 2 #let format_timeframe(d_us) = { 3 if d_us == "forever" { 4 "forever" 5 } else { 6 let us = int(d_us) 7 let s = calc.quo(us, 1000000) 8 let m = calc.quo(s, 60) 9 let h = calc.quo(m, 60) 10 let d = calc.quo(h, 24) 11 let w = calc.quo(d, 7) 12 13 if calc.rem(us, 1000000) == 0 { 14 if calc.rem(s, 60) == 0 { 15 if calc.rem(m, 60) == 0 { 16 if calc.rem(h, 24) == 0 { 17 if calc.rem(d, 7) == 0 { 18 str(w) + " week" + if w != 1 { "s" } else { "" } 19 } else { 20 str(d) + " day" + if d != 1 { "s" } else { "" } 21 } 22 } else { 23 str(h) + " hour" + if h != 1 { "s" } else { "" } 24 } 25 } else { 26 str(m) + " minute" + if m != 1 { "s" } else { "" } 27 } 28 } else { 29 str(s) + " s" 30 } 31 } else { 32 str(us) + " μs" 33 } 34 } 35 } 36 37 // Helper function to format timestamp; ignores leap seconds (too variable) 38 // Helper function to format a Taler timestamp object {t_s: ...} 39 #let format_timestamp(ts) = { 40 if type(ts) == dictionary and "t_s" in ts { 41 let t_s = ts.t_s 42 if t_s == "never" { 43 "never" 44 } else { 45 // Convert Unix timestamp to human-readable format 46 let seconds = int(t_s) 47 let days_since_epoch = calc.quo(seconds, 86400) 48 let remaining_seconds = calc.rem(seconds, 86400) 49 let hours = calc.quo(remaining_seconds, 3600) 50 let minutes = calc.quo(calc.rem(remaining_seconds, 3600), 60) 51 let secs = calc.rem(remaining_seconds, 60) 52 53 // Helper to check if year is leap year 54 let is_leap(y) = { 55 calc.rem(y, 4) == 0 and (calc.rem(y, 100) != 0 or calc.rem(y, 400) == 0) 56 } 57 58 // Calculate year, month, day 59 let year = 1970 60 let days_left = days_since_epoch 61 62 // Find the year 63 let done = false 64 while not done { 65 let days_in_year = if is_leap(year) { 366 } else { 365 } 66 if days_left >= days_in_year { 67 days_left = days_left - days_in_year 68 year = year + 1 69 } else { 70 done = true 71 } 72 } 73 74 // Days in each month 75 let days_in_months = if is_leap(year) { 76 (31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) 77 } else { 78 (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) 79 } 80 81 // Find month and day 82 let month = 1 83 for days_in_month in days_in_months { 84 if days_left >= days_in_month { 85 days_left = days_left - days_in_month 86 month = month + 1 87 } else { 88 break 89 } 90 } 91 let day = days_left + 1 92 93 // Format with leading zeros 94 let m_str = if month < 10 { "0" + str(month) } else { str(month) } 95 let d_str = if day < 10 { "0" + str(day) } else { str(day) } 96 let h_str = if hours < 10 { "0" + str(hours) } else { str(hours) } 97 let min_str = if minutes < 10 { "0" + str(minutes) } else { str(minutes) } 98 let s_str = if secs < 10 { "0" + str(secs) } else { str(secs) } 99 100 str(year) + "-" + m_str + "-" + d_str + " " + h_str + ":" + min_str + ":" + s_str + " UTC" 101 } 102 } else { 103 str(ts) 104 } 105 } 106 107 108 // Helper function to format timestamp; ignores leap seconds (too variable) 109 // Coarsen based on r_us (a value in milliseconds). 110 // If r_us is a minute, do not show seconds. 111 // If r_us is an hour, do not show minutes or seconds 112 // If r_us is a day, do not show hours, minutes or seconds. 113 // If r_us is a month, do not show days. 114 // If r_us is a year, do not show months. 115 // If mini is true, truly minify the result only showing 116 // the unit around the r_us granularity. 117 #let format_round_timestamp(ts, r_us, mini) = { 118 if type(ts) == dictionary and "t_s" in ts { 119 let t_s = ts.t_s 120 if t_s == "never" { 121 "never" 122 } else { 123 // Convert Unix timestamp to human-readable format 124 let seconds = int(t_s) 125 let days_since_epoch = calc.quo(seconds, 86400) 126 let remaining_seconds = calc.rem(seconds, 86400) 127 let hours = calc.quo(remaining_seconds, 3600) 128 let minutes = calc.quo(calc.rem(remaining_seconds, 3600), 60) 129 let secs = calc.rem(remaining_seconds, 60) 130 131 // Helper to check if year is leap year 132 let is_leap(y) = { 133 calc.rem(y, 4) == 0 and (calc.rem(y, 100) != 0 or calc.rem(y, 400) == 0) 134 } 135 136 // Calculate year, month, day 137 let year = 1970 138 let days_left = days_since_epoch 139 140 // Find the year 141 let done = false 142 while not done { 143 let days_in_year = if is_leap(year) { 366 } else { 365 } 144 if days_left >= days_in_year { 145 days_left = days_left - days_in_year 146 year = year + 1 147 } else { 148 done = true 149 } 150 } 151 152 // Days in each month 153 let days_in_months = if is_leap(year) { 154 (31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) 155 } else { 156 (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) 157 } 158 159 // Find month and day 160 let month = 1 161 for days_in_month in days_in_months { 162 if days_left >= days_in_month { 163 days_left = days_left - days_in_month 164 month = month + 1 165 } else { 166 break 167 } 168 } 169 let day = days_left + 1 170 171 // Format with leading zeros 172 let m_str = if month < 10 { "0" + str(month) } else { str(month) } 173 let d_str = if day < 10 { "0" + str(day) } else { str(day) } 174 let h_str = if hours < 10 { "0" + str(hours) } else { str(hours) } 175 let min_str = if minutes < 10 { "0" + str(minutes) } else { str(minutes) } 176 let s_str = if secs < 10 { "0" + str(secs) } else { str(secs) } 177 178 // Define thresholds in microseconds 179 let minute_us = 60 * 1000 * 1000 180 let hour_us = 60 * minute_us 181 let day_us = 24 * hour_us 182 let month_us = 30 * day_us // Approximate: 30 days 183 let year_us = 365 * day_us // Approximate: 365 days 184 185 // Build timestamp string based on r_us thresholds 186 if r_us >= year_us { 187 // Year or more: show only year 188 str(year) 189 } else if r_us >= month_us { 190 // Month or more: show year and month 191 if (mini) { m_str } else { str(year) + "-" + m_str } 192 } else if r_us >= day_us { 193 // Day or more: show year, month, and day 194 if (mini) { d_str + "d" } else { str(year) + "-" + m_str + "-" + d_str } 195 } else if r_us >= hour_us { 196 // Hour or more: show up to hours 197 if (mini) { h_str + "h" } else { str(year) + "-" + m_str + "-" + d_str + " " + h_str + ":00 UTC" } 198 } else if r_us >= minute_us { 199 // Minute or more: show up to minutes 200 if (mini) { min_str + "m" } else { str(year) + "-" + m_str + "-" + d_str + " " + h_str + ":" + min_str + " UTC" } 201 } else { 202 // Less than a minute: show full precision 203 if (mini) { s_str + "s" } else { str(year) + "-" + m_str + "-" + d_str + " " + h_str + ":" + min_str + ":" + s_str + " UTC" } 204 } 205 } 206 } else { 207 str(ts) 208 } 209 } 210 211 // Format a Taler amount. 212 // Taler serialises amounts as a plain string "CURRENCY:VALUE.FRACTION", 213 // e.g. "EUR:5.50". If the value is null / none we render a dash. 214 #let format_amount(a) = { 215 if a == none { 216 "-" 217 } else { 218 // a is already a human-readable string like "EUR:5.50" 219 // Note: eventually we want it formatted *nicely*, but this 220 // needs the currency rendering data, so probably better done 221 // on the C side (where we don't yet have an amount renderer). 222 str(a).replace(":", " ") 223 } 224 }