How to convert Milliseconds to "X mins, x seconds" in Java?

JavaTime

Java Problem Overview


I want to record the time using System.currentTimeMillis() when a user begins something in my program. When he finishes, I will subtract the current System.currentTimeMillis() from the start variable, and I want to show them the time elapsed using a human readable format such as "XX hours, XX mins, XX seconds" or even "XX mins, XX seconds" because its not likely to take someone an hour.

What's the best way to do this?

Java Solutions


Solution 1 - Java

Use the java.util.concurrent.TimeUnit class:

String.format("%d min, %d sec", 
    TimeUnit.MILLISECONDS.toMinutes(millis),
    TimeUnit.MILLISECONDS.toSeconds(millis) - 
    TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis))
);

Note: TimeUnit is part of the Java 1.5 specification, but toMinutes was added as of Java 1.6.

To add a leading zero for values 0-9, just do:

String.format("%02d min, %02d sec", 
    TimeUnit.MILLISECONDS.toMinutes(millis),
    TimeUnit.MILLISECONDS.toSeconds(millis) - 
    TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis))
);

If TimeUnit or toMinutes are unsupported (such as on Android before API version 9), use the following equations:

int seconds = (int) (milliseconds / 1000) % 60 ;
int minutes = (int) ((milliseconds / (1000*60)) % 60);
int hours   = (int) ((milliseconds / (1000*60*60)) % 24);
//etc...

Solution 2 - Java

Based on @siddhadev's answer, I wrote a function which converts milliseconds to a formatted string:

   /**
	 * Convert a millisecond duration to a string format
	 * 
	 * @param millis A duration to convert to a string form
	 * @return A string of the form "X Days Y Hours Z Minutes A Seconds".
	 */
	public static String getDurationBreakdown(long millis) {
        if(millis < 0) {
            throw new IllegalArgumentException("Duration must be greater than zero!");
        }

        long days = TimeUnit.MILLISECONDS.toDays(millis);
	    millis -= TimeUnit.DAYS.toMillis(days);
	    long hours = TimeUnit.MILLISECONDS.toHours(millis);
	    millis -= TimeUnit.HOURS.toMillis(hours);
        long minutes = TimeUnit.MILLISECONDS.toMinutes(millis);
	    millis -= TimeUnit.MINUTES.toMillis(minutes);
	    long seconds = TimeUnit.MILLISECONDS.toSeconds(millis);
		
	    StringBuilder sb = new StringBuilder(64);
	    sb.append(days);
	    sb.append(" Days ");
	    sb.append(hours);
	    sb.append(" Hours ");
	    sb.append(minutes);
	    sb.append(" Minutes ");
	    sb.append(seconds);
	    sb.append(" Seconds");
		
	    return(sb.toString());
	}

Solution 3 - Java

long time = 1536259;

return (new SimpleDateFormat("mm:ss:SSS")).format(new Date(time));

Prints: > 25:36:259

Solution 4 - Java

Uhm... how many milliseconds are in a second? And in a minute? Division is not that hard.

int seconds = (int) ((milliseconds / 1000) % 60);
int minutes = (int) ((milliseconds / 1000) / 60);

Continue like that for hours, days, weeks, months, year, decades, whatever.

Solution 5 - Java

Using the java.time package in Java 8:

Instant start = Instant.now();
Thread.sleep(63553);
Instant end = Instant.now();
System.out.println(Duration.between(start, end));

Output is in ISO 8601 Duration format: PT1M3.553S (1 minute and 3.553 seconds).

Solution 6 - Java

I would not pull in the extra dependency just for that (division is not that hard, after all), but if you are using Commons Lang anyway, there are the DurationFormatUtils.

Example Usage (adapted from here):

import org.apache.commons.lang3.time.DurationFormatUtils

public String getAge(long value) {
	long currentTime = System.currentTimeMillis();
	long age = currentTime - value;
	String ageString = DurationFormatUtils.formatDuration(age, "d") + "d";
	if ("0d".equals(ageString)) {
		ageString = DurationFormatUtils.formatDuration(age, "H") + "h";
		if ("0h".equals(ageString)) {
			ageString = DurationFormatUtils.formatDuration(age, "m") + "m";
			if ("0m".equals(ageString)) {
				ageString = DurationFormatUtils.formatDuration(age, "s") + "s";
				if ("0s".equals(ageString)) {
					ageString = age + "ms";
				}
			}
		}
	}
	return ageString;
}	

Example:

long lastTime = System.currentTimeMillis() - 2000;
System.out.println("Elapsed time: " + getAge(lastTime)); 

//Output: 2s

Note: To get millis from two LocalDateTime objects you can use:

long age = ChronoUnit.MILLIS.between(initTime, LocalDateTime.now())

Solution 7 - Java

Either hand divisions, or use the SimpleDateFormat API.

long start = System.currentTimeMillis();
// do your work...
long elapsed = System.currentTimeMillis() - start;
DateFormat df = new SimpleDateFormat("HH 'hours', mm 'mins,' ss 'seconds'");
df.setTimeZone(TimeZone.getTimeZone("GMT+0"));
System.out.println(df.format(new Date(elapsed)));

Edit by Bombe: It has been shown in the comments that this approach only works for smaller durations (i.e. less than a day).

Solution 8 - Java

Just to add more info if you want to format like: HH:mm:ss

0 <= HH <= infinite

0 <= mm < 60

0 <= ss < 60

use this:

int h = (int) ((startTimeInMillis / 1000) / 3600);
int m = (int) (((startTimeInMillis / 1000) / 60) % 60);
int s = (int) ((startTimeInMillis / 1000) % 60);

I just had this issue now and figured this out

Solution 9 - Java

Shortest solution:

Here's probably the shortest which also deals with time zones.

System.out.printf("%tT", millis-TimeZone.getDefault().getRawOffset());

Which outputs for example:

00:18:32

Explanation:

%tT is the time formatted for the 24-hour clock as %tH:%tM:%tS.

%tT also accepts longs as input, so no need to create a Date. printf() will simply print the time specified in milliseconds, but in the current time zone therefore we have to subtract the raw offset of the current time zone so that 0 milliseconds will be 0 hours and not the time offset value of the current time zone.

Note #1: If you need the result as a String, you can get it like this:

String t = String.format("%tT", millis-TimeZone.getDefault().getRawOffset());

Note #2: This only gives correct result if millis is less than a day because the day part is not included in the output.

Solution 10 - Java

I think the best way is:

String.format("%d min, %d sec", 
	TimeUnit.MILLISECONDS.toSeconds(length)/60,
	TimeUnit.MILLISECONDS.toSeconds(length) % 60 );

Solution 11 - Java

Joda-Time

Using Joda-Time:

DateTime startTime = new DateTime();

// do something

DateTime endTime = new DateTime();
Duration duration = new Duration(startTime, endTime);
Period period = duration.toPeriod().normalizedStandard(PeriodType.time());
System.out.println(PeriodFormat.getDefault().print(period));

Solution 12 - Java

Revisiting @brent-nash contribution, we could use modulus function instead of subtractions and use String.format method for the result string:

  /**
   * Convert a millisecond duration to a string format
   * 
   * @param millis A duration to convert to a string form
   * @return A string of the form "X Days Y Hours Z Minutes A Seconds B Milliseconds".
   */
   public static String getDurationBreakdown(long millis) {
       if (millis < 0) {
          throw new IllegalArgumentException("Duration must be greater than zero!");
       }
    
       long days = TimeUnit.MILLISECONDS.toDays(millis);
       long hours = TimeUnit.MILLISECONDS.toHours(millis) % 24;
       long minutes = TimeUnit.MILLISECONDS.toMinutes(millis) % 60;
       long seconds = TimeUnit.MILLISECONDS.toSeconds(millis) % 60;
       long milliseconds = millis % 1000;
    
       return String.format("%d Days %d Hours %d Minutes %d Seconds %d Milliseconds",
                            days, hours, minutes, seconds, milliseconds);
   }

Solution 13 - Java

For those who looking for Kotlin code:

fun converter(millis: Long): String =
        String.format(
            "%02d : %02d : %02d",
            TimeUnit.MILLISECONDS.toHours(millis),
            TimeUnit.MILLISECONDS.toMinutes(millis) - TimeUnit.HOURS.toMinutes(
                TimeUnit.MILLISECONDS.toHours(millis)
            ),
            TimeUnit.MILLISECONDS.toSeconds(millis) - TimeUnit.MINUTES.toSeconds(
                TimeUnit.MILLISECONDS.toMinutes(millis)
            )
        )

Sample output: 09 : 10 : 26

Solution 14 - Java

for Android below API 9

(String.format("%d hr %d min, %d sec", millis/(1000*60*60), (millis%(1000*60*60))/(1000*60), ((millis%(1000*60*60))%(1000*60))/1000)) 

Solution 15 - Java

My simple calculation:

String millisecToTime(int millisec) {
    int sec = millisec/1000;
    int second = sec % 60;
    int minute = sec / 60;
    if (minute >= 60) {
        int hour = minute / 60;
        minute %= 60;
        return hour + ":" + (minute < 10 ? "0" + minute : minute) + ":" + (second < 10 ? "0" + second : second);
    }
    return minute + ":" + (second < 10 ? "0" + second : second);
}

Happy coding :)

Solution 16 - Java

Firstly, System.currentTimeMillis() and Instant.now() are not ideal for timing. They both report the wall-clock time, which the computer doesn't know precisely, and which can move erratically, including going backwards if for example the NTP daemon corrects the system time. If your timing happens on a single machine then you should instead use System.nanoTime().

Secondly, from Java 8 onwards java.time.Duration is the best way to represent a duration:

long start = System.nanoTime();
// do things...
long end = System.nanoTime();
Duration duration = Duration.ofNanos(end - start);
System.out.println(duration); // Prints "PT18M19.511627776S"
System.out.printf("%d Hours %d Minutes %d Seconds%n",
        duration.toHours(), duration.toMinutes() % 60, duration.getSeconds() % 60);
// prints "0 Hours 18 Minutes 19 Seconds"

Solution 17 - Java

For small times, less than an hour, I prefer:

long millis = ...

System.out.printf("%1$TM:%1$TS", millis);
// or
String str = String.format("%1$TM:%1$TS", millis);

for longer intervalls:

private static final long HOUR = TimeUnit.HOURS.toMillis(1);
...
if (millis < HOUR) {
    System.out.printf("%1$TM:%1$TS%n", millis);
} else {
    System.out.printf("%d:%2$TM:%2$TS%n", millis / HOUR, millis % HOUR);
}

Solution 18 - Java

    long startTime = System.currentTimeMillis();
    // do your work...
    long endTime=System.currentTimeMillis();
    long diff=endTime-startTime;       
    long hours=TimeUnit.MILLISECONDS.toHours(diff);
    diff=diff-(hours*60*60*1000);
    long min=TimeUnit.MILLISECONDS.toMinutes(diff);
    diff=diff-(min*60*1000);
    long seconds=TimeUnit.MILLISECONDS.toSeconds(diff);
    //hour, min and seconds variables contains the time elapsed on your work

Solution 19 - Java

Here is an answer based on Brent Nash answer, Hope that helps !

public static String getDurationBreakdown(long millis)
{
	String[] units = {" Days ", " Hours ", " Minutes ", " Seconds "};
	Long[] values = new Long[units.length];
    if(millis < 0)
    {
        throw new IllegalArgumentException("Duration must be greater than zero!");
    }

    values[0] = TimeUnit.MILLISECONDS.toDays(millis);
    millis -= TimeUnit.DAYS.toMillis(values[0]);
    values[1] = TimeUnit.MILLISECONDS.toHours(millis);
    millis -= TimeUnit.HOURS.toMillis(values[1]);
    values[2] = TimeUnit.MILLISECONDS.toMinutes(millis);
    millis -= TimeUnit.MINUTES.toMillis(values[2]);
    values[3] = TimeUnit.MILLISECONDS.toSeconds(millis);

    StringBuilder sb = new StringBuilder(64);
    boolean startPrinting = false;
    for(int i = 0; i < units.length; i++){
    	if( !startPrinting && values[i] != 0)
    		startPrinting = true;
    	if(startPrinting){
    		sb.append(values[i]);
    		sb.append(units[i]);
    	}
    }

    return(sb.toString());
}

Solution 20 - Java

for correct strings ("1hour, 3sec", "3 min" but not "0 hour, 0 min, 3 sec") i write this code:

int seconds = (int)(millis / 1000) % 60 ;
int minutes = (int)((millis / (1000*60)) % 60);
int hours = (int)((millis / (1000*60*60)) % 24);
int days = (int)((millis / (1000*60*60*24)) % 365);
int years = (int)(millis / 1000*60*60*24*365);

ArrayList<String> timeArray = new ArrayList<String>();

if(years > 0)   
    timeArray.add(String.valueOf(years)   + "y");

if(days > 0)    
    timeArray.add(String.valueOf(days) + "d");

if(hours>0)   
    timeArray.add(String.valueOf(hours) + "h");

if(minutes>0) 
    timeArray.add(String.valueOf(minutes) + "min");

if(seconds>0) 
    timeArray.add(String.valueOf(seconds) + "sec");

String time = "";
for (int i = 0; i < timeArray.size(); i++) 
{
    time = time + timeArray.get(i);
    if (i != timeArray.size() - 1)
        time = time + ", ";
}

if (time == "")
  time = "0 sec";

Solution 21 - Java

If you know the time difference would be less than an hour, then you can use following code:

    Calendar c1 = Calendar.getInstance();
    Calendar c2 = Calendar.getInstance();

    c2.add(Calendar.MINUTE, 51);
    
    long diff = c2.getTimeInMillis() - c1.getTimeInMillis();

    c2.set(Calendar.MINUTE, 0);
    c2.set(Calendar.HOUR, 0);
    c2.set(Calendar.SECOND, 0);

    DateFormat df = new SimpleDateFormat("mm:ss");
    long diff1 = c2.getTimeInMillis() + diff;
    System.out.println(df.format(new Date(diff1)));

It will result to: 51:00

Solution 22 - Java

This answer is similar to some answers above. However, I feel that it would be beneficial because, unlike other answers, this will remove any extra commas or whitespace and handles abbreviation.

/**
 * Converts milliseconds to "x days, x hours, x mins, x secs"
 * 
 * @param millis
 *            The milliseconds
 * @param longFormat
 *            {@code true} to use "seconds" and "minutes" instead of "secs" and "mins"
 * @return A string representing how long in days/hours/minutes/seconds millis is.
 */
public static String millisToString(long millis, boolean longFormat) {
    if (millis < 1000) {
        return String.format("0 %s", longFormat ? "seconds" : "secs");
    }
    String[] units = {
            "day", "hour", longFormat ? "minute" : "min", longFormat ? "second" : "sec"
    };
    long[] times = new long[4];
    times[0] = TimeUnit.DAYS.convert(millis, TimeUnit.MILLISECONDS);
    millis -= TimeUnit.MILLISECONDS.convert(times[0], TimeUnit.DAYS);
    times[1] = TimeUnit.HOURS.convert(millis, TimeUnit.MILLISECONDS);
    millis -= TimeUnit.MILLISECONDS.convert(times[1], TimeUnit.HOURS);
    times[2] = TimeUnit.MINUTES.convert(millis, TimeUnit.MILLISECONDS);
    millis -= TimeUnit.MILLISECONDS.convert(times[2], TimeUnit.MINUTES);
    times[3] = TimeUnit.SECONDS.convert(millis, TimeUnit.MILLISECONDS);
    StringBuilder s = new StringBuilder();
    for (int i = 0; i < 4; i++) {
        if (times[i] > 0) {
            s.append(String.format("%d %s%s, ", times[i], units[i], times[i] == 1 ? "" : "s"));
        }
    }
    return s.toString().substring(0, s.length() - 2);
}

/**
 * Converts milliseconds to "x days, x hours, x mins, x secs"
 * 
 * @param millis
 *            The milliseconds
 * @return A string representing how long in days/hours/mins/secs millis is.
 */
public static String millisToString(long millis) {
    return millisToString(millis, false);
}

Solution 23 - Java

This is easier in Java 9:

	Duration elapsedTime = Duration.ofMillis(millisDiff );
    String humanReadableElapsedTime = String.format(
            "%d hours, %d mins, %d seconds",
            elapsedTime.toHours(),
            elapsedTime.toMinutesPart(),
            elapsedTime.toSecondsPart());

This produces a string like 0 hours, 39 mins, 9 seconds.

If you want to round to whole seconds before formatting:

    elapsedTime = elapsedTime.plusMillis(500).truncatedTo(ChronoUnit.SECONDS);

To leave out the hours if they are 0:

	long hours = elapsedTime.toHours();
	String humanReadableElapsedTime;
	if (hours == 0) {
		humanReadableElapsedTime = String.format(
				"%d mins, %d seconds",
				elapsedTime.toMinutesPart(),
				elapsedTime.toSecondsPart());

	} else {
		humanReadableElapsedTime = String.format(
				"%d hours, %d mins, %d seconds",
				hours,
				elapsedTime.toMinutesPart(),
				elapsedTime.toSecondsPart());
	}

Now we can have for example 39 mins, 9 seconds.

To print minutes and seconds with leading zero to make them always two digits, just insert 02 into the relevant format specifiers, thus:

    String humanReadableElapsedTime = String.format(
            "%d hours, %02d mins, %02d seconds",
            elapsedTime.toHours(),
            elapsedTime.toMinutesPart(),
            elapsedTime.toSecondsPart());

Now we can have for example 0 hours, 39 mins, 09 seconds.

Solution 24 - Java

There is a problem. When milliseconds is 59999, actually it is 1 minute but it will be computed as 59 seconds and 999 milliseconds is lost.

Here is a modified version based on previous answers, which can solve this loss:

public static String formatTime(long millis) {
	long seconds = Math.round((double) millis / 1000);
	long hours = TimeUnit.SECONDS.toHours(seconds);
	if (hours > 0)
		seconds -= TimeUnit.HOURS.toSeconds(hours);
	long minutes = seconds > 0 ? TimeUnit.SECONDS.toMinutes(seconds) : 0;
	if (minutes > 0)
		seconds -= TimeUnit.MINUTES.toSeconds(minutes);
	return hours > 0 ? String.format("%02d:%02d:%02d", hours, minutes, seconds) : String.format("%02d:%02d", minutes, seconds);
}

Solution 25 - Java

I have covered this in another answer but you can do:

public static Map<TimeUnit,Long> computeDiff(Date date1, Date date2) {
    long diffInMillies = date2.getTime() - date1.getTime();
    List<TimeUnit> units = new ArrayList<TimeUnit>(EnumSet.allOf(TimeUnit.class));
    Collections.reverse(units);
    Map<TimeUnit,Long> result = new LinkedHashMap<TimeUnit,Long>();
    long milliesRest = diffInMillies;
    for ( TimeUnit unit : units ) {
        long diff = unit.convert(milliesRest,TimeUnit.MILLISECONDS);
        long diffInMilliesForUnit = unit.toMillis(diff);
        milliesRest = milliesRest - diffInMilliesForUnit;
        result.put(unit,diff);
    }
    return result;
}

The output is something like Map:{DAYS=1, HOURS=3, MINUTES=46, SECONDS=40, MILLISECONDS=0, MICROSECONDS=0, NANOSECONDS=0}, with the units ordered.

It's up to you to figure out how to internationalize this data according to the target locale.

Solution 26 - Java

I modified @MyKuLLSKI 's answer and added plurlization support. I took out seconds because I didn't need them, though feel free to re-add it if you need it.

public static String intervalToHumanReadableTime(int intervalMins) {
	
	if(intervalMins <= 0) {
		return "0";
	} else {
		
		long intervalMs = intervalMins * 60 * 1000;
		
	    long days = TimeUnit.MILLISECONDS.toDays(intervalMs);
	    intervalMs -= TimeUnit.DAYS.toMillis(days);
	    long hours = TimeUnit.MILLISECONDS.toHours(intervalMs);
	    intervalMs -= TimeUnit.HOURS.toMillis(hours);
	    long minutes = TimeUnit.MILLISECONDS.toMinutes(intervalMs);

	    StringBuilder sb = new StringBuilder(12);
	    
	    if (days >= 1) {
	    	sb.append(days).append(" day").append(pluralize(days)).append(", ");
	    }

	    if (hours >= 1) {
	    	sb.append(hours).append(" hour").append(pluralize(hours)).append(", ");
	    }

	    if (minutes >= 1) {
	    	sb.append(minutes).append(" minute").append(pluralize(minutes));
	    } else {
	    	sb.delete(sb.length()-2, sb.length()-1);
	    }

	    return(sb.toString());			
		
	}

}

public static String pluralize(long val) {
	return (Math.round(val) > 1 ? "s" : "");
}

Solution 27 - Java

Use java.util.concurrent.TimeUnit, and use this simple method:

private static long timeDiff(Date date, Date date2, TimeUnit unit) {
	long milliDiff=date2.getTime()-date.getTime();
	long unitDiff = unit.convert(milliDiff, TimeUnit.MILLISECONDS);
	return unitDiff; 
}

For example:

SimpleDateFormat sdf = new SimpleDateFormat("yy/MM/dd HH:mm:ss");  
Date firstDate = sdf.parse("06/24/2017 04:30:00");
Date secondDate = sdf.parse("07/24/2017 05:00:15");
Date thirdDate = sdf.parse("06/24/2017 06:00:15");

System.out.println("days difference: "+timeDiff(firstDate,secondDate,TimeUnit.DAYS));
System.out.println("hours difference: "+timeDiff(firstDate,thirdDate,TimeUnit.HOURS));
System.out.println("minutes difference: "+timeDiff(firstDate,thirdDate,TimeUnit.MINUTES));
System.out.println("seconds difference: "+timeDiff(firstDate,thirdDate,TimeUnit.SECONDS));

Solution 28 - Java

Solution 29 - Java

This topic has been well covered, I just wanted to share my functions perhaps you can make use of these rather than importing an entire library.

    public long getSeconds(ms) {
        return (ms/1000%60);
    }
    public long getMinutes(ms) {
        return (ms/(1000*60)%60);
    }
    public long getHours(ms) {
        return ((ms/(1000*60*60))%24);
    }

I needed to convert the entire milliseconds to seconds in a project, in case you need this functionality here you go:

	public long convertToSeconds(long ms) {
		//                    hours                       minutes              seconds
		return ((((ms/(1000*60*60))%24)*60)*60) + ((ms/(1000*60) %60)*60) + (ms/1000%60);
	}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAliView Question on Stackoverflow
Solution 1 - JavasiddhadevView Answer on Stackoverflow
Solution 2 - JavaBrent Writes CodeView Answer on Stackoverflow
Solution 3 - JavaDamienView Answer on Stackoverflow
Solution 4 - JavaBombeView Answer on Stackoverflow
Solution 5 - JavaVitalii FedorenkoView Answer on Stackoverflow
Solution 6 - JavaThiloView Answer on Stackoverflow
Solution 7 - JavacadrianView Answer on Stackoverflow
Solution 8 - JavafredcrsView Answer on Stackoverflow
Solution 9 - JavaiczaView Answer on Stackoverflow
Solution 10 - JavaXteraView Answer on Stackoverflow
Solution 11 - JavadogbaneView Answer on Stackoverflow
Solution 12 - JavaJose TepedinoView Answer on Stackoverflow
Solution 13 - JavaKaaveh MohamediView Answer on Stackoverflow
Solution 14 - JavaPradeepView Answer on Stackoverflow
Solution 15 - JavaShohan Ahmed SijanView Answer on Stackoverflow
Solution 16 - JavaMikeFHayView Answer on Stackoverflow
Solution 17 - Javauser85421View Answer on Stackoverflow
Solution 18 - JavaFathah Rehman PView Answer on Stackoverflow
Solution 19 - JavaReda BouaichiView Answer on Stackoverflow
Solution 20 - JavaJohnView Answer on Stackoverflow
Solution 21 - JavaGourav SinglaView Answer on Stackoverflow
Solution 22 - JavaJared RummlerView Answer on Stackoverflow
Solution 23 - JavaOle V.V.View Answer on Stackoverflow
Solution 24 - JavaHexiseView Answer on Stackoverflow
Solution 25 - JavaSebastien LorberView Answer on Stackoverflow
Solution 26 - JavabitstreamView Answer on Stackoverflow
Solution 27 - JavaAramis Rodríguez BlancoView Answer on Stackoverflow
Solution 28 - JavaTOUDIdelView Answer on Stackoverflow
Solution 29 - JavaderpdewpView Answer on Stackoverflow