<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Link Jen</title>
    <description>The latest articles on DEV Community by Link Jen (@link_jen_c53c47fa5d5c9e36).</description>
    <link>https://dev.to/link_jen_c53c47fa5d5c9e36</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4239193%2Fab893010-4881-40f5-ac37-c26562ea54b8.png</url>
      <title>DEV Community: Link Jen</title>
      <link>https://dev.to/link_jen_c53c47fa5d5c9e36</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/link_jen_c53c47fa5d5c9e36"/>
    <language>en</language>
    <item>
      <title>How to evaluate and choose AI tools for your workflow</title>
      <dc:creator>Link Jen</dc:creator>
      <pubDate>Wed, 11 Nov 2026 15:22:56 +0000</pubDate>
      <link>https://dev.to/link_jen_c53c47fa5d5c9e36/how-to-evaluate-and-choose-ai-tools-for-your-workflow-3cap</link>
      <guid>https://dev.to/link_jen_c53c47fa5d5c9e36/how-to-evaluate-and-choose-ai-tools-for-your-workflow-3cap</guid>
      <description>&lt;p&gt;Two tools can look interchangeable on a feature list and still differ on export options, rate limits, or whether you can run them yourself. The useful work is checking a product against the job you actually have.&lt;/p&gt;

&lt;p&gt;The checklist below follows the public review criteria on &lt;a href="https://ainovatools.com" rel="noopener noreferrer"&gt;AINovaTools&lt;/a&gt;. The category counts and scoring thresholds I cite were taken from the site on 11 November 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the job
&lt;/h2&gt;

&lt;p&gt;Write the task in one sentence. "Review a pull request in this repository" is enough. So is "generate a 20-second product clip from a still image," or "clone a voice for an internal demo." Then name the constraints that would make a tool unusable: budget, data residency, self-hosting, a required API, the platform (browser, CLI, or desktop), how much you will trust an unattended agent.&lt;/p&gt;

&lt;p&gt;Only after that should you browse a category. AINovaTools groups tools into 16 categories, from productivity and coding to education and data analysis. The labels help with discovery, but they aren't exclusive. On 11 November 2026, coding listed 97 tools and productivity listed 117, while the all-tools index reported 257. If you add the category counts together you will overstate the catalog, because one listing can carry more than one tag. If the task spans two categories, shortlist from both and compare the tools against the same constraints.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can you actually use the product?
&lt;/h2&gt;

&lt;p&gt;Before building a feature matrix, apply a few hard filters. The AINovaTools editorial policy rejects a candidate, regardless of score, when any of the following is true:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The website is unreachable, permanently broken, or redirects in a malicious way.&lt;/li&gt;
&lt;li&gt;The product impersonates another, clones one without a clear difference, shows misleading pricing, or has an unidentifiable operator.&lt;/li&gt;
&lt;li&gt;There is malware, an illegal use case, or a clear privacy or IP problem.&lt;/li&gt;
&lt;li&gt;It is a duplicate of an existing listing with no explainable unique value.&lt;/li&gt;
&lt;li&gt;The page is only an affiliate or ad destination, and the product cannot actually be used or verified.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I use the same list. If I can't tell who operates the tool, can't reach a working product, or can't find a pricing or terms page, I stop. A polished landing page isn't enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Score what you can verify
&lt;/h2&gt;

&lt;p&gt;AINovaTools scores candidates on a 100-point scale across nine weighted dimensions. You don't need to copy the weights. The questions are still a useful frame:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Search demand (20): are people looking for this tool, or only for the problem it claims to solve?&lt;/li&gt;
&lt;li&gt;Audience fit (15): does it serve the workflow you wrote down, or a different buyer?&lt;/li&gt;
&lt;li&gt;Freshness (10): is it maintained?&lt;/li&gt;
&lt;li&gt;Differentiation (15): can you learn limits or failure modes that the vendor page leaves out?&lt;/li&gt;
&lt;li&gt;Documentation (10): are the site, pricing, docs, and terms verifiable?&lt;/li&gt;
&lt;li&gt;Credibility (10): is the operator identifiable, and does the product look stable?&lt;/li&gt;
&lt;li&gt;Commercial intent (10): do people in this category compare options and actually buy?&lt;/li&gt;
&lt;li&gt;Partnership potential (5): if something breaks, is there support, an issue tracker, or a status page?&lt;/li&gt;
&lt;li&gt;Maintainability (5): can you afford to keep checking price and features as they change?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On that site, 80 or above is a priority listing, 65-79 enters a candidate pool, and below 65 is not published. For my own shortlist I use a simpler bar: if documentation, operator identity, or pricing can't be verified, I skip the tool, no matter how interesting the demo is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify pricing against the official source
&lt;/h2&gt;

&lt;p&gt;Headline prices are a poor summary of what you'll pay. The review methodology on AINovaTools requires every published price, quota, and limit to be checked against the official pricing page or docs, and linked from the tool page. Pages carry a Last verified date. If the vendor changes pricing after that date, the vendor's site is treated as authoritative until the next refresh. Free-tier limits and overage behavior are recorded where the vendor documents them. Those details usually matter more than the plan name.&lt;/p&gt;

&lt;p&gt;When I evaluate a tool, I open the official pricing or billing documentation, not a roundup post. I write down the free-tier cap, what happens when you exceed it, whether a credit card is required, and whether API usage is billed separately from the app, plus the date I checked. If a third-party page and the vendor disagree, I trust the vendor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tests you actually ran
&lt;/h2&gt;

&lt;p&gt;For its priority pages (top listings, comparisons, and "best of" picks), AINovaTools goes beyond the vendor's marketing. An editor signs up and runs a realistic task, such as a coding agent on a real repository, a text-to-speech tool on real script audio, or an OCR model on real documents, and records limits such as rate caps, quality drop-offs, missing export options, and regional restrictions. Where something has not been tested first-hand, the page is supposed to say so.&lt;/p&gt;

&lt;p&gt;I do the same thing. A screenshot of a happy path isn't a test. Run one task that looks like your actual work, and write down what failed. If you don't have time to test a tool, keep it in an "untested" column.&lt;/p&gt;

&lt;p&gt;User ratings deserve the same caution. AINovaTools states that ratings on tool pages come from real user submissions on that site, and that it does not display aggregate scores it cannot trace or import fabricated review counts. An unsourced star rating shouldn't decide the purchase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plan for the tool to change
&lt;/h2&gt;

&lt;p&gt;AINovaTools re-verifies high-traffic and high-change pages on roughly a 30-day cycle, and the rest on a 90-day cycle. Tools whose websites go offline are unpublished or clearly flagged. Meaningful changes to pricing or claims update the Last verified date. Factual errors can be reported with a URL, a description, and a source; the team says it acknowledges reports within five business days.&lt;/p&gt;

&lt;p&gt;Put a reminder on the tools you actually pay for, and re-check pricing, limits, and export paths every month or quarter. If a vendor disappears or a model is retired, you want time to switch before a workflow breaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't treat placement or payment as a review
&lt;/h2&gt;

&lt;p&gt;Directories and "best of" lists often mix editorial and commercial placements. AINovaTools states that payment never changes editorial judgment: a paid submission can buy a faster review, placement visibility, and analytics, but not a better score, rating, or review conclusion. Paid and affiliate links are marked as sponsored or nofollow. Listing does not require a backlink, and editors are described as having no financial stake in listed tools.&lt;/p&gt;

&lt;p&gt;If a write-up doesn't disclose a commercial relationship, treat a strong recommendation with more skepticism. A disclosed placement can still be accurate, but it isn't independent evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist
&lt;/h2&gt;

&lt;p&gt;Before you adopt a tool, you should be able to answer yes, or have a documented exception, to these:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I can describe the job in one sentence and name the constraints that would make a tool unusable.&lt;/li&gt;
&lt;li&gt;I can identify the operator and reach a working product, not only a landing page.&lt;/li&gt;
&lt;li&gt;Pricing, quotas, and overage are copied from an official source, with the date I checked.&lt;/li&gt;
&lt;li&gt;I have either run a realistic task myself or clearly marked the tool as untested.&lt;/li&gt;
&lt;li&gt;I know what happens if the tool is down, the model changes, or I need to export my data.&lt;/li&gt;
&lt;li&gt;I have not treated a paid placement, an unsourced star rating, or a category rank as a substitute for the above.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>ai</category>
      <category>tools</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How to convert decimal hours to hours and minutes</title>
      <dc:creator>Link Jen</dc:creator>
      <pubDate>Wed, 11 Nov 2026 15:21:41 +0000</pubDate>
      <link>https://dev.to/link_jen_c53c47fa5d5c9e36/how-to-convert-decimal-hours-to-hours-and-minutes-451b</link>
      <guid>https://dev.to/link_jen_c53c47fa5d5c9e36/how-to-convert-decimal-hours-to-hours-and-minutes-451b</guid>
      <description>&lt;p&gt;Payroll systems and time trackers often store a workday as a decimal. Seven hours and forty-five minutes becomes 7.75. That's convenient for arithmetic: 7.75 plus 1.25 is exactly 9.0. It's less convenient when you have to print the same value as 7:45 on a timesheet.&lt;/p&gt;

&lt;p&gt;The conversion is short. The bugs I've run into are almost always in the same places: how you round a fractional minute, what you do with a negative adjustment, and whether a total over 24 hours should wrap like a clock or stay a duration.&lt;/p&gt;

&lt;h2&gt;
  
  
  The formula
&lt;/h2&gt;

&lt;p&gt;Treat the decimal as a duration, not a time of day.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Multiply the decimal hours by 60 to get a total number of minutes.&lt;/li&gt;
&lt;li&gt;Round that total to an integer, using an explicit rounding rule.&lt;/li&gt;
&lt;li&gt;Split the integer with division and remainder: hours are the quotient, minutes are the remainder.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So 7.75 × 60 = 465 minutes, and 465 ÷ 60 is 7 hours with 45 left over. Result: 7:45.&lt;/p&gt;

&lt;p&gt;The same steps work for a weekly total. 25.5 × 60 = 1,530 minutes, which is 25 hours and 30 minutes, not 1:30 the next morning.&lt;/p&gt;

&lt;p&gt;If you only need to convert a single value and don't want to open a spreadsheet, &lt;a href="https://decimaltotime.com" rel="noopener noreferrer"&gt;Decimal to Time&lt;/a&gt; will do that in a browser. The snippets below are for when the conversion has to live in your own code or timesheets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rounding
&lt;/h2&gt;

&lt;p&gt;Almost every off-by-one I've seen in this conversion comes from rounding. 1.995 hours is 1 hour plus 0.995 × 60 = 59.7 minutes. If you take the integer part of the hours first and then round the leftover minutes, 59.7 becomes 60, and you have to carry that extra hour. Forget the carry and you print &lt;code&gt;1:60&lt;/code&gt;, which isn't a valid time.&lt;/p&gt;

&lt;p&gt;It's more reliable to round the total minutes first, then split:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1.995 × 60 = 119.7&lt;/li&gt;
&lt;li&gt;Round to 120&lt;/li&gt;
&lt;li&gt;120 ÷ 60 = 2 hours, remainder 0&lt;/li&gt;
&lt;li&gt;Result: 2:00&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You also need to pick a rounding mode and stick to it. The snippets below use half away from zero: 0.5 minutes rounds to 1, and -0.5 minutes rounds to -1. That's usually what payroll and timesheets expect.&lt;/p&gt;

&lt;p&gt;Don't assume a language's built-in &lt;code&gt;round&lt;/code&gt; already does that.&lt;/p&gt;

&lt;p&gt;Python 3's &lt;code&gt;round()&lt;/code&gt; uses banker's rounding (ties go to the even integer), so &lt;code&gt;round(2.5)&lt;/code&gt; is 2 and &lt;code&gt;round(3.5)&lt;/code&gt; is 4. The Python snippet uses &lt;code&gt;Decimal.ROUND_HALF_UP&lt;/code&gt; instead, which is half away from zero.&lt;/p&gt;

&lt;p&gt;JavaScript's &lt;code&gt;Math.round&lt;/code&gt; rounds ties toward +Infinity, so &lt;code&gt;Math.round(1.5)&lt;/code&gt; is 2 but &lt;code&gt;Math.round(-1.5)&lt;/code&gt; is -1. The snippet rounds the absolute value first, then restores the sign, so ties behave the same in both directions.&lt;/p&gt;

&lt;p&gt;Floating-point representation can also nudge a value that should be exact, especially after you sum many entries. In Python, &lt;code&gt;Decimal(str(value))&lt;/code&gt; keeps ordinary timesheet values in base 10. In JavaScript, rounding the total minutes is usually enough. If you need exact decimal arithmetic, use a decimal library, or store minutes as integers from the start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Negative values
&lt;/h2&gt;

&lt;p&gt;Negative decimal hours show up as adjustments: a correction of -1.25 hours, a balance of -0.5 hours, or a reversal of a previous entry. Dropping the sign, or taking a clock-style modulo of a negative number, will give you the wrong duration.&lt;/p&gt;

&lt;p&gt;A convention that works in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For one hour or more, keep minutes in the range 0 to 59 and put the sign on the hours. -7.75 becomes &lt;code&gt;-7:45&lt;/code&gt;, not &lt;code&gt;-8:15&lt;/code&gt; and not &lt;code&gt;7:-45&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Between 0 and -1 hour, there are no hours to hold the sign. -0.5 hours is 0 hours and -30 minutes, which you should still format as &lt;code&gt;-0:30&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you only attach the sign to the hours component, &lt;code&gt;sign * 0&lt;/code&gt; is just 0, and the minus sign disappears. Both snippets below treat that case explicitly.&lt;/p&gt;

&lt;p&gt;If a negative duration isn't valid in your system, reject it with an error. Don't silently take the absolute value.&lt;/p&gt;

&lt;h2&gt;
  
  
  More than 24 hours
&lt;/h2&gt;

&lt;p&gt;7.75 is a duration. So is 25.5, and so is 100.25 (100 hours and 15 minutes). These values appear in weekly timesheets, project totals, and overtime reports.&lt;/p&gt;

&lt;p&gt;Don't reduce the hours modulo 24 unless you are converting a duration into a clock time on a 24-hour face, which is a different problem. &lt;code&gt;25:30&lt;/code&gt; as a duration and &lt;code&gt;01:30&lt;/code&gt; as a time of day mean different things. Excel's default &lt;code&gt;h:mm&lt;/code&gt; format wraps at 24 hours; you need &lt;code&gt;[h]:mm&lt;/code&gt; to keep the full hour count. The code snippets do not wrap.&lt;/p&gt;

&lt;h2&gt;
  
  
  JavaScript
&lt;/h2&gt;

&lt;p&gt;These functions convert a finite number of decimal hours to &lt;code&gt;{ hours, minutes }&lt;/code&gt; and format them as &lt;code&gt;H:MM&lt;/code&gt;. I ran the examples with Node.js.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;decimalHoursToHm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s2"&gt;"number"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isFinite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"value must be a finite number"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// Math.round is half toward +Infinity, so round the absolute&lt;/span&gt;
  &lt;span class="c1"&gt;// value and restore the sign (half away from zero).&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;sign&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;totalMinutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sign&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;absolute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;totalMinutes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;hours&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;absolute&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;minutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;absolute&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sign&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;minutes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minutes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minutes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;formatHm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minutes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;sign&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hours&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;minutes&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;"-"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sb"&gt;`${sign}${Math.abs(hours)}:${String(Math.abs(minutes)).padStart(2, "0")}`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;formatHm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;decimalHoursToHm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;7.75&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// "7:45"&lt;/span&gt;
&lt;span class="nf"&gt;formatHm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;decimalHoursToHm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.995&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// "2:00"&lt;/span&gt;
&lt;span class="nf"&gt;formatHm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;decimalHoursToHm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;25.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// "25:30"&lt;/span&gt;
&lt;span class="nf"&gt;formatHm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;decimalHoursToHm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;7.75&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// "-7:45"&lt;/span&gt;
&lt;span class="nf"&gt;formatHm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;decimalHoursToHm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// "-0:30"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Python
&lt;/h2&gt;

&lt;p&gt;The Python version does the same conversion with &lt;code&gt;decimal.Decimal&lt;/code&gt;, so the arithmetic stays in base 10.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;decimal&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;ROUND_HALF_UP&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decimal_hours_to_hm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;hours_decimal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;total_minutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hours_decimal&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_integral_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rounding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="no"&gt;ROUND_HALF_UP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sign&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;total_minutes&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;divmod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_minutes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sign&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;hours&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;minutes&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minutes&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minutes&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;format_hm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;sign&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"-"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;hours&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;minutes&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"{sign}{abs(hours)}:{abs(minutes):02d}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Decimal(str(value))&lt;/code&gt; is deliberate. &lt;code&gt;Decimal(7.75)&lt;/code&gt; would first go through a binary float; &lt;code&gt;Decimal("7.75")&lt;/code&gt; or &lt;code&gt;Decimal(str(7.75))&lt;/code&gt; keeps the decimal you actually wrote. The examples above produce the same strings as the JavaScript version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Excel and Google Sheets
&lt;/h2&gt;

&lt;p&gt;If the decimal hours are in cell &lt;code&gt;A1&lt;/code&gt;, this formula rounds to the nearest minute and formats the result as a duration, including values over 24 hours and negative values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=IF(A1&amp;lt;0,"-","")&amp;amp;TEXT(ROUND(ABS(A1)*60,0)/1440,"[h]:mm")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ABS(A1)*60&lt;/code&gt; is the total minutes; &lt;code&gt;ROUND(...,0)&lt;/code&gt; is the same nearest-minute step as in the code. Dividing by 1440 (minutes in a day) makes an Excel or Sheets time value. &lt;code&gt;[h]:mm&lt;/code&gt; prints hours as a duration. Plain &lt;code&gt;h:mm&lt;/code&gt; would wrap 25.5 into &lt;code&gt;1:30&lt;/code&gt;. The leading &lt;code&gt;IF&lt;/code&gt; prepends a minus sign, because &lt;code&gt;TEXT&lt;/code&gt; on a negative time is unreliable (Excel often shows &lt;code&gt;#####&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;If you'd rather not use &lt;code&gt;TEXT&lt;/code&gt;, you can build the string from integer hours and minutes. This works in current Excel (&lt;code&gt;LET&lt;/code&gt;) and in Google Sheets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=LET(m,ROUND(A1*60,0),IF(m&amp;lt;0,"-","")&amp;amp;INT(ABS(m)/60)&amp;amp;":"&amp;amp;TEXT(MOD(ABS(m),60),"00"))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In older Excel without &lt;code&gt;LET&lt;/code&gt;, put &lt;code&gt;=ROUND(A1*60,0)&lt;/code&gt; in a helper cell and use the same &lt;code&gt;IF&lt;/code&gt; / &lt;code&gt;INT&lt;/code&gt; / &lt;code&gt;MOD&lt;/code&gt; pattern on that cell.&lt;/p&gt;

&lt;p&gt;Avoid &lt;code&gt;=TEXT(A1/24,"h:mm")&lt;/code&gt; for timesheets. It skips an explicit minute-rounding step and wraps at 24 hours, so 25.5 becomes 1:30, and 1.995 can display as 1:59 or 2:00 depending on the remainder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Convert total minutes first, then split into hours and minutes, so a rounded 60 minutes becomes an extra hour.&lt;/li&gt;
&lt;li&gt;State the rounding mode. Half away from zero is a reasonable default for payroll; don't assume &lt;code&gt;round()&lt;/code&gt; or &lt;code&gt;Math.round&lt;/code&gt; already does that.&lt;/li&gt;
&lt;li&gt;Treat the result as a duration. Only wrap at 24 hours if you are actually formatting a clock time.&lt;/li&gt;
&lt;li&gt;Preserve the sign, including for values between -1 and 0 hours.&lt;/li&gt;
&lt;li&gt;In spreadsheets, use &lt;code&gt;[h]:mm&lt;/code&gt;, not &lt;code&gt;h:mm&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>productivity</category>
      <category>javascript</category>
      <category>python</category>
      <category>excel</category>
    </item>
  </channel>
</rss>
