Add to date or time in AutoHotkey - autohotkey

I was looking for an inbuild ahk function that allows the user to add days, months, years or even time to an existing day thus converting it correctly to a new month if the day count reaches 32. I didn't find anything, so I came up with this little solution:
; returns an array [year, month, day, hour, minute, second]
DateTimeAdd(v_a_now,yearPlus=0,monthPlus=0,dayPlus=0,hrPlus=0,minPlus=0,secPlus=0) {
daysInMonth := { 1:31, 2:28, 3:31, 4:30, 5:31, 6:30, 7:31, 8:31, 9:30, 10:31, 11:30, 12:31 }
; Parse data from an A_NOW type format
; If you pass your custom "A_NOW" format remember that numbers < 10 are expected to have a leading 0
day := SubStr(v_a_now,7,2) + dayPlus
month := SubStr(v_a_now,5,2) + monthPlus
year := SubStr(v_a_now,1,4) + yearPlus
hours := SubStr(v_a_now,9,2) + hrPlus
minutes := SubStr(v_a_now,11,2) + minPlus
seconds := SubStr(v_a_now,13,2) + secPlus
; Start formatting
if(seconds >= 60) {
tadd := seconds / 60
seconds -= Floor(tadd) * 60
minutes += Floor(tadd)
}
if(minutes >= 60) {
tadd := minutes / 60
minutes -= Floor(tadd) * 60
hours += Floor(tadd)
}
if(hours >= 24) {
tadd := hours / 24
hours -= Floor(tadd) * 24
day += Floor(tadd)
}
; We have to format the month first in order to be able to format the days later on
if(month >= 13) {
tadd := month / 12
month -= Floor(tadd) * 12
year += Floor(tadd)
}
; Assmuning the number of days is an absurd number like 23424 we need to go through each month and subtract the max. amount of days from that month
cond := true
while(cond) {
; Get the number of max. days in this current month (sadly no loop years included :< )
max_days_in_this_month := daysInMonth[month]
; If the number of days i.e. 42 is great than 31 for example in January
if(day > max_days_in_this_month) {
; Subtract max. allowed days in month from day
day -= max_days_in_this_month
; New Year?
if(month == 12) {
month := 1
year++
} else {
month++
}
} else {
cond := false
}
}
; Add leading zero to numbers
return_array := [year, month, day, hours, minutes, seconds]
i := 2
while(i != return_array.MaxIndex()+1) {
thisIteration := return_array[i]
if(thisIteration <= 9) {
return_array[i] := "0" thisIteration
}
i++
}
; Done formatting
; For testing
;~ msg := return_array[1] "/" return_array[2] "/" return_array[3] " " return_array[4] ":" return_array[5] ":" return_array[6]
;~ msgbox % msg
return return_array
}
Sadly this function does not take loop years into aspect. Do you guys know any better alternatives?

Check out EnvAdd at https://autohotkey.com/docs/commands/EnvAdd.htm
EnvAdd, Var, Value, TimeUnits
equivalent to
Var += Value, TimeUnits
EnvAdd sets a date variable Var (in YYYYMMDDHH24MISS format) to the sum of itself plus the given date value Value using the timeunits parameter.
Example:
newDate := %A_Now% ; or whatever your starting date is
EnvAdd, newDate, 20, days
NewDate += 11, days
MsgBox, %newDate% ; The answer will be the date 31 days from now.

Related

Output dates based on an array

Need Help. I have an associative array which has the days of the week. I know very little about arrays. Each day has a 1 or 0 in the array below. I am trying to output dates and use them to generate tasks. I'm sure can this be done easily, but I lack the experience.
I want to output only dates which have 1's as shown in the array below.
The first task is due in 4 days.
I need to view dates up to 3 days ahead, so if today is Tuesday, and start is on Friday, then the Friday task date won't appear until Wednesday.
Today := A_now
WeekDays_Array := {Sun: 0, Mon: 0, Tue: 1, Wed: 0, Thu: 1, Fri: 1, Sat: 0}
I am pretty sure this is most of it. The suggestion to use the array above made the most sense.
The script uses Wkday (which is A_Now) as the start day but I think that can be changed by substituting it for a specific start day
PastDue := []
DueBy := []
;~ Weekday_Array := {Sunday: 0, Monday: 0, Tuesday: 1, Wednesday: 0, Thursday: 1, Friday: 1, Saturday: 0}
Weekday_Array := [1,1,1,1,1,1,1] ;Represents Sun-Saturday
AssignTask:
FormatTime, WkDay, MyTime, WDay
For i, value in Weekday_Array{
DueDate =
if (i > WkDay && Value = 1){
x++
DueDate += (x), days
FormatTime, DueDate, % DueDate, MM/dd/yy
DueBy.InsertAt(i,DueDate)
}
if (i = WkDay && Value = 1){
FormatTime, DueDate, % A_Now, MM/dd/yy
DueBy.InsertAt(i,DueDate)
}
if (i < WkDay && Value = 1){
y--
Pastdate =
PastDate += (y) , days
NewDate := PastDate
NewDate += 7, days
FormatTime, PastDate, % PastDate, MM/dd/yy
FormatTime, NewDate, % NewDate, MM/dd/yy
PastDue.InsertAt(i,PastDate)
DueBy.insertAt(i,NewDate)
}
}
For i, PastDue in PastDue
PastDueDates .= PastDue "`n"
Sort, PastDueDates
PastDueDates := "Past due dates so far this week `n`n" PastDueDates
MsgBox, 0x1000,, % PastDueDates
For i, DueDate in DueBy
DueDates .= DueDate "`n"
Sort, DueDates
DueDates := "All upcoming due dates based criteria `n`n" DueDates
MsgBox, 0x1000,, % DueDates
sort, DueDates
FormatTime, xDay1, % A_Now, MM/dd/yy
xDay2 += 1, days
FormatTime, xDay2, % xDay2, MM/dd/yy
xDay3 += 2, days
FormatTime, xDay3, % xDay3, MM/dd/yy
For i, DueDate in DueBy{
if (DueDate = xDay1 || DueDate = xDay2 || DueDate = xDay3)
XDays .= DueDate "`n"
}
xDays := "Only view the next 3 days from today `n`n" xDays
MsgBox, 0x1000,, % xDays
;~ return
*esc::
ExitApp
return

Converting hours to milliseconds

I am trying to convert some minutes and hours to milliseconds but the code below does not work.
I have tried lower numbers and it seamed to kind of work, is there a number limit in ahk?
ConvertedTimeInMilliSeconds := %Hours% * 3600000 + %Minutes% * 60000
I was expecting Hours to be 7200000 if the input would be two but instead its blank.
This is a common misunderstanding at the beginning.
I'll make this example, this are valid syntaxes:
Command, OutputVar, %InputVar%
Function(OutputVar, InputVar)
OutputVar := Function(InputVar)
OutputVar = %InputVar% Literal text
OutputVar := InputVar . " Literal text"
So in your code above, remove the % because you used an := assignment. Assigning through = can be always worked around into := and is probably the most recommended.
Variable names in an expression are not enclosed in percent signs:
Hours = 15
Minutes = 48
ConvertedTimeInMilliSeconds := (Hours * 3600000) + (Minutes * 60000)
MsgBox, % ConvertedTimeInMilliSeconds
or use a function:
MsgBox, % ConvertTimeInMilliSeconds(15, 48)
ConvertTimeInMilliSeconds(Hours, Minutes){
TimeInMilliSeconds := (Hours * 3600000) + (Minutes * 60000)
return TimeInMilliSeconds
}

Crystal Reports convert number to text

Using Crystal Reports version 8 and I need to convert a numbervar to stringvar. I have tried ToText() (with all variations of capitalization) and CStr() (also with various capitalizations) and each time CR tells me "A number is required here" and moves the cursor to the beginning of my else block. Ultimately trying to convert hours and minutes both stored as NumberVars to strings so I can display "8h 30m" instead of 8.50.
Here is what I have for the formula so far:
if {Collect2000Log.LogCode} = "0002" then 0
else
(
NumberVar OldTime := ((DateDiff("n",{#NextTime},{Collect2000Log.LogWhen})/60)*-1);
NumberVar Hours;
NumberVar Minutes;
StringVar strHours;
StringVar strMinutes;
StringVar NewTime;
//Extract the number of hours
Hours := Int(OldTime);
//Get the decimal portion for minutes
Minutes := Remainder(OldTime, 1) * 100;
//Divide the minutes by 60 to increase the number of hours
Hours := Hours + Int(Minutes / 60);
//Get the remainder for the number of minutes left over
Minutes := Remainder(Minutes, 60);
//Convert hours & mins to strings
strHours := ToText(Hours);
strMinutes := ToText(Minutes):
NewTime := strHours & "h " & strMinutes & "m";
);
And now when I add this formula CR says "The end ) is missing" and I'm stumped. I was able to get around that once but now not looking so hopeful.
Any help would be much appreciated!
Problem is simple... In an IF and Else you need to return same data type but you are returning a number through IF and a String through Else hence the error a Number is required at the starting of the else block.. so convert the output of If to string like below.
if {Collect2000Log.LogCode} = "0002"
then ToText(0)
else
(
NumberVar OldTime := ((DateDiff("n",{#NextTime},{Collect2000Log.LogWhen})/60)*-1);
NumberVar Hours;
NumberVar Minutes;
StringVar strHours;
StringVar strMinutes;
StringVar NewTime;
//Extract the number of hours
Hours := Int(OldTime);
//Get the decimal portion for minutes
Minutes := Remainder(OldTime, 1) * 100;
//Divide the minutes by 60 to increase the number of hours
Hours := Hours + Int(Minutes / 60);
//Get the remainder for the number of minutes left over
Minutes := Remainder(Minutes, 60);
//Convert hours & mins to strings
strHours := ToText(Hours);
strMinutes := ToText(Minutes):
NewTime := strHours & "h " & strMinutes & "m";
);

Comparing two Date values in ActionScript - possible to compare whole day values?

I need to be able to compare the number of whole days between two dates in ActionScript, is this possible?
I'd like to test if one date is 7 days or less after today, and if so is it one day or less (if it's before today this also counts).
The workaround I have in place is using the .time part of the date field:
// Get the diffence between the current date and the due date
var dateDiff:Date = new Date();
dateDiff.setTime (dueDate.time - currentDate.time);
if (dateDiff.time < ( 1 * 24 * 60 * 60 * 1000 ))
return "Date is within 1 day");
else if (dateDiff.time < ( 7 * 24 * 60 * 60 * 1000 ))
return "Date is within 7 days");
As I say - this is only a workaround, I'd like a permanent solution to allow me to check the number of whole days between 2 dates. Is this possible?
Thanks
var daysDifference:Number = Math.floor((dueDate.time-currentDate.time)/(1000*60*60*24));
if (daysDifference < 2)
return "Date is within 1 day";
else if (daysDifference < 8)
return "Date is within 7 days";

How to convert milliseconds into human readable form?

I need to convert an arbitrary amount of milliseconds into Days, Hours, Minutes Second.
For example: 10 Days, 5 hours, 13 minutes, 1 second.
Well, since nobody else has stepped up, I'll write the easy code to do this:
x = ms / 1000
seconds = x % 60
x /= 60
minutes = x % 60
x /= 60
hours = x % 24
x /= 24
days = x
I'm just glad you stopped at days and didn't ask for months. :)
Note that in the above, it is assumed that / represents truncating integer division. If you use this code in a language where / represents floating point division, you will need to manually truncate the results of the division as needed.
Let A be the amount of milliseconds. Then you have:
seconds=(A/1000)%60
minutes=(A/(1000*60))%60
hours=(A/(1000*60*60))%24
and so on (% is the modulus operator).
Hope this helps.
Both solutions below use javascript (I had no idea the solution was language agnostic!). Both solutions will need to be extended if capturing durations > 1 month.
Solution 1: Use the Date object
var date = new Date(536643021);
var str = '';
str += date.getUTCDate()-1 + " days, ";
str += date.getUTCHours() + " hours, ";
str += date.getUTCMinutes() + " minutes, ";
str += date.getUTCSeconds() + " seconds, ";
str += date.getUTCMilliseconds() + " millis";
console.log(str);
Gives:
"6 days, 5 hours, 4 minutes, 3 seconds, 21 millis"
Libraries are helpful, but why use a library when you can re-invent the wheel! :)
Solution 2: Write your own parser
var getDuration = function(millis){
var dur = {};
var units = [
{label:"millis", mod:1000},
{label:"seconds", mod:60},
{label:"minutes", mod:60},
{label:"hours", mod:24},
{label:"days", mod:31}
];
// calculate the individual unit values...
units.forEach(function(u){
millis = (millis - (dur[u.label] = (millis % u.mod))) / u.mod;
});
// convert object to a string representation...
var nonZero = function(u){ return dur[u.label]; };
dur.toString = function(){
return units
.reverse()
.filter(nonZero)
.map(function(u){
return dur[u.label] + " " + (dur[u.label]==1?u.label.slice(0,-1):u.label);
})
.join(', ');
};
return dur;
};
Creates a "duration" object, with whatever fields you require.
Formatting a timestamp then becomes simple...
console.log(getDuration(536643021).toString());
Gives:
"6 days, 5 hours, 4 minutes, 3 seconds, 21 millis"
Apache Commons Lang has a DurationFormatUtils that has very helpful methods like formatDurationWords.
You should use the datetime functions of whatever language you're using, but, just for fun here's the code:
int milliseconds = someNumber;
int seconds = milliseconds / 1000;
int minutes = seconds / 60;
seconds %= 60;
int hours = minutes / 60;
minutes %= 60;
int days = hours / 24;
hours %= 24;
This is a method I wrote. It takes an integer milliseconds value and returns a human-readable String:
public String convertMS(int ms) {
int seconds = (int) ((ms / 1000) % 60);
int minutes = (int) (((ms / 1000) / 60) % 60);
int hours = (int) ((((ms / 1000) / 60) / 60) % 24);
String sec, min, hrs;
if(seconds<10) sec="0"+seconds;
else sec= ""+seconds;
if(minutes<10) min="0"+minutes;
else min= ""+minutes;
if(hours<10) hrs="0"+hours;
else hrs= ""+hours;
if(hours == 0) return min+":"+sec;
else return hrs+":"+min+":"+sec;
}
function convertTime(time) {
var millis= time % 1000;
time = parseInt(time/1000);
var seconds = time % 60;
time = parseInt(time/60);
var minutes = time % 60;
time = parseInt(time/60);
var hours = time % 24;
var out = "";
if(hours && hours > 0) out += hours + " " + ((hours == 1)?"hr":"hrs") + " ";
if(minutes && minutes > 0) out += minutes + " " + ((minutes == 1)?"min":"mins") + " ";
if(seconds && seconds > 0) out += seconds + " " + ((seconds == 1)?"sec":"secs") + " ";
if(millis&& millis> 0) out += millis+ " " + ((millis== 1)?"msec":"msecs") + " ";
return out.trim();
}
In java
public static String formatMs(long millis) {
long hours = TimeUnit.MILLISECONDS.toHours(millis);
long mins = TimeUnit.MILLISECONDS.toMinutes(millis);
long secs = TimeUnit.MILLISECONDS.toSeconds(millis);
return String.format("%dh %d min, %d sec",
hours,
mins - TimeUnit.HOURS.toMinutes(hours),
secs - TimeUnit.MINUTES.toSeconds(mins)
);
}
Gives something like this:
12h 1 min, 34 sec
I would suggest using whatever date/time functions/libraries your language/framework of choice provides. Also check out string formatting functions as they often provide easy ways to pass date/timestamps and output a human readable string format.
Your choices are simple:
Write the code to do the conversion (ie, divide by milliSecondsPerDay to get days and use the modulus to divide by milliSecondsPerHour to get hours and use the modulus to divide by milliSecondsPerMinute and divide by 1000 for seconds. milliSecondsPerMinute = 60000, milliSecondsPerHour = 60 * milliSecondsPerMinute, milliSecondsPerDay = 24 * milliSecondsPerHour.
Use an operating routine of some kind. UNIX and Windows both have structures that you can get from a Ticks or seconds type value.
Long serverUptimeSeconds =
(System.currentTimeMillis() - SINCE_TIME_IN_MILLISECONDS) / 1000;
String serverUptimeText =
String.format("%d days %d hours %d minutes %d seconds",
serverUptimeSeconds / 86400,
( serverUptimeSeconds % 86400) / 3600 ,
((serverUptimeSeconds % 86400) % 3600 ) / 60,
((serverUptimeSeconds % 86400) % 3600 ) % 60
);
Why just don't do something like this:
var ms = 86400;
var seconds = ms / 1000; //86.4
var minutes = seconds / 60; //1.4400000000000002
var hours = minutes / 60; //0.024000000000000004
var days = hours / 24; //0.0010000000000000002
And dealing with float precision e.g. Number(minutes.toFixed(5)) //1.44
I'm not able to comment first answer to your question, but there's a small mistake. You should use parseInt or Math.floor to convert floating point numbers to integer, i
var days, hours, minutes, seconds, x;
x = ms / 1000;
seconds = Math.floor(x % 60);
x /= 60;
minutes = Math.floor(x % 60);
x /= 60;
hours = Math.floor(x % 24);
x /= 24;
days = Math.floor(x);
Personally, I use CoffeeScript in my projects and my code looks like that:
getFormattedTime : (ms)->
x = ms / 1000
seconds = Math.floor x % 60
x /= 60
minutes = Math.floor x % 60
x /= 60
hours = Math.floor x % 24
x /= 24
days = Math.floor x
formattedTime = "#{seconds}s"
if minutes then formattedTime = "#{minutes}m " + formattedTime
if hours then formattedTime = "#{hours}h " + formattedTime
formattedTime
This is a solution. Later you can split by ":" and take the values of the array
/**
* Converts milliseconds to human readeable language separated by ":"
* Example: 190980000 --> 2:05:3 --> 2days 5hours 3min
*/
function dhm(t){
var cd = 24 * 60 * 60 * 1000,
ch = 60 * 60 * 1000,
d = Math.floor(t / cd),
h = '0' + Math.floor( (t - d * cd) / ch),
m = '0' + Math.round( (t - d * cd - h * ch) / 60000);
return [d, h.substr(-2), m.substr(-2)].join(':');
}
var delay = 190980000;
var fullTime = dhm(delay);
console.log(fullTime);
Long expireTime = 69l;
Long tempParam = 0l;
Long seconds = math.mod(expireTime, 60);
tempParam = expireTime - seconds;
expireTime = tempParam/60;
Long minutes = math.mod(expireTime, 60);
tempParam = expireTime - minutes;
expireTime = expireTime/60;
Long hours = math.mod(expireTime, 24);
tempParam = expireTime - hours;
expireTime = expireTime/24;
Long days = math.mod(expireTime, 30);
system.debug(days + '.' + hours + ':' + minutes + ':' + seconds);
This should print: 0.0:1:9
Here's my solution using TimeUnit.
UPDATE: I should point out that this is written in groovy, but Java is almost identical.
def remainingStr = ""
/* Days */
int days = MILLISECONDS.toDays(remainingTime) as int
remainingStr += (days == 1) ? '1 Day : ' : "${days} Days : "
remainingTime -= DAYS.toMillis(days)
/* Hours */
int hours = MILLISECONDS.toHours(remainingTime) as int
remainingStr += (hours == 1) ? '1 Hour : ' : "${hours} Hours : "
remainingTime -= HOURS.toMillis(hours)
/* Minutes */
int minutes = MILLISECONDS.toMinutes(remainingTime) as int
remainingStr += (minutes == 1) ? '1 Minute : ' : "${minutes} Minutes : "
remainingTime -= MINUTES.toMillis(minutes)
/* Seconds */
int seconds = MILLISECONDS.toSeconds(remainingTime) as int
remainingStr += (seconds == 1) ? '1 Second' : "${seconds} Seconds"
A flexible way to do it :
(Not made for current date but good enough for durations)
/**
convert duration to a ms/sec/min/hour/day/week array
#param {int} msTime : time in milliseconds
#param {bool} fillEmpty(optional) : fill array values even when they are 0.
#param {string[]} suffixes(optional) : add suffixes to returned values.
values are filled with missings '0'
#return {int[]/string[]} : time values from higher to lower(ms) range.
*/
var msToTimeList=function(msTime,fillEmpty,suffixes){
suffixes=(suffixes instanceof Array)?suffixes:[]; //suffixes is optional
var timeSteps=[1000,60,60,24,7]; // time ranges : ms/sec/min/hour/day/week
timeSteps.push(1000000); //add very big time at the end to stop cutting
var result=[];
for(var i=0;(msTime>0||i<1||fillEmpty)&&i<timeSteps.length;i++){
var timerange = msTime%timeSteps[i];
if(typeof(suffixes[i])=="string"){
timerange+=suffixes[i]; // add suffix (converting )
// and fill zeros :
while( i<timeSteps.length-1 &&
timerange.length<((timeSteps[i]-1)+suffixes[i]).length )
timerange="0"+timerange;
}
result.unshift(timerange); // stack time range from higher to lower
msTime = Math.floor(msTime/timeSteps[i]);
}
return result;
};
NB : you could also set timeSteps as parameter if you want to control the time ranges.
how to use (copy an test):
var elsapsed = Math.floor(Math.random()*3000000000);
console.log( "elsapsed (labels) = "+
msToTimeList(elsapsed,false,["ms","sec","min","h","days","weeks"]).join("/") );
console.log( "half hour : "+msToTimeList(elsapsed,true)[3]<30?"first":"second" );
console.log( "elsapsed (classic) = "+
msToTimeList(elsapsed,false,["","","","","",""]).join(" : ") );
I suggest to use http://www.ocpsoft.org/prettytime/ library..
it's very simple to get time interval in human readable form like
PrettyTime p = new PrettyTime();
System.out.println(p.format(new Date()));
it will print like "moments from now"
other example
PrettyTime p = new PrettyTime());
Date d = new Date(System.currentTimeMillis());
d.setHours(d.getHours() - 1);
String ago = p.format(d);
then string ago = "1 hour ago"
In python 3 you could achieve your goal by using the following snippet:
from datetime import timedelta
ms = 536643021
td = timedelta(milliseconds=ms)
print(str(td))
# --> 6 days, 5:04:03.021000
Timedelta documentation: https://docs.python.org/3/library/datetime.html#datetime.timedelta
Source of the __str__ method of timedelta str: https://github.com/python/cpython/blob/33922cb0aa0c81ebff91ab4e938a58dfec2acf19/Lib/datetime.py#L607
Here is more precise method in JAVA , I have implemented this simple logic , hope this will help you:
public String getDuration(String _currentTimemilliSecond)
{
long _currentTimeMiles = 1;
int x = 0;
int seconds = 0;
int minutes = 0;
int hours = 0;
int days = 0;
int month = 0;
int year = 0;
try
{
_currentTimeMiles = Long.parseLong(_currentTimemilliSecond);
/** x in seconds **/
x = (int) (_currentTimeMiles / 1000) ;
seconds = x ;
if(seconds >59)
{
minutes = seconds/60 ;
if(minutes > 59)
{
hours = minutes/60;
if(hours > 23)
{
days = hours/24 ;
if(days > 30)
{
month = days/30;
if(month > 11)
{
year = month/12;
Log.d("Year", year);
Log.d("Month", month%12);
Log.d("Days", days % 30);
Log.d("hours ", hours % 24);
Log.d("Minutes ", minutes % 60);
Log.d("Seconds ", seconds % 60);
return "Year "+year + " Month "+month%12 +" Days " +days%30 +" hours "+hours%24 +" Minutes "+minutes %60+" Seconds "+seconds%60;
}
else
{
Log.d("Month", month);
Log.d("Days", days % 30);
Log.d("hours ", hours % 24);
Log.d("Minutes ", minutes % 60);
Log.d("Seconds ", seconds % 60);
return "Month "+month +" Days " +days%30 +" hours "+hours%24 +" Minutes "+minutes %60+" Seconds "+seconds%60;
}
}
else
{
Log.d("Days", days );
Log.d("hours ", hours % 24);
Log.d("Minutes ", minutes % 60);
Log.d("Seconds ", seconds % 60);
return "Days " +days +" hours "+hours%24 +" Minutes "+minutes %60+" Seconds "+seconds%60;
}
}
else
{
Log.d("hours ", hours);
Log.d("Minutes ", minutes % 60);
Log.d("Seconds ", seconds % 60);
return "hours "+hours+" Minutes "+minutes %60+" Seconds "+seconds%60;
}
}
else
{
Log.d("Minutes ", minutes);
Log.d("Seconds ", seconds % 60);
return "Minutes "+minutes +" Seconds "+seconds%60;
}
}
else
{
Log.d("Seconds ", x);
return " Seconds "+seconds;
}
}
catch (Exception e)
{
Log.e(getClass().getName().toString(), e.toString());
}
return "";
}
private Class Log
{
public static void d(String tag , int value)
{
System.out.println("##### [ Debug ] ## "+tag +" :: "+value);
}
}
A solution using awk:
$ ms=10000001; awk -v ms=$ms 'BEGIN {x=ms/1000;
s=x%60; x/=60;
m=x%60; x/=60;
h=x%60;
printf("%02d:%02d:%02d.%03d\n", h, m, s, ms%1000)}'
02:46:40.001
This one leaves out 0 values. With tests.
const toTimeString = (value, singularName) =>
`${value} ${singularName}${value !== 1 ? 's' : ''}`;
const readableTime = (ms) => {
const days = Math.floor(ms / (24 * 60 * 60 * 1000));
const daysMs = ms % (24 * 60 * 60 * 1000);
const hours = Math.floor(daysMs / (60 * 60 * 1000));
const hoursMs = ms % (60 * 60 * 1000);
const minutes = Math.floor(hoursMs / (60 * 1000));
const minutesMs = ms % (60 * 1000);
const seconds = Math.round(minutesMs / 1000);
const data = [
[days, 'day'],
[hours, 'hour'],
[minutes, 'minute'],
[seconds, 'second'],
];
return data
.filter(([value]) => value > 0)
.map(([value, name]) => toTimeString(value, name))
.join(', ');
};
// Tests
const hundredDaysTwentyHoursFiftyMinutesThirtySeconds = 8715030000;
const oneDayTwoHoursEightMinutesTwelveSeconds = 94092000;
const twoHoursFiftyMinutes = 10200000;
const oneMinute = 60000;
const fortySeconds = 40000;
const oneSecond = 1000;
const oneDayTwelveSeconds = 86412000;
const test = (result, expected) => {
console.log(expected, '- ' + (result === expected));
};
test(readableTime(
hundredDaysTwentyHoursFiftyMinutesThirtySeconds
), '100 days, 20 hours, 50 minutes, 30 seconds');
test(readableTime(
oneDayTwoHoursEightMinutesTwelveSeconds
), '1 day, 2 hours, 8 minutes, 12 seconds');
test(readableTime(
twoHoursFiftyMinutes
), '2 hours, 50 minutes');
test(readableTime(
oneMinute
), '1 minute');
test(readableTime(
fortySeconds
), '40 seconds');
test(readableTime(
oneSecond
), '1 second');
test(readableTime(
oneDayTwelveSeconds
), '1 day, 12 seconds');