All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.continual.util.data.HumanReadableHelper Maven / Gradle / Ivy

There is a newer version: 0.3.14
Show newest version
/*
 *	Copyright 2019, Continual.io
 *
 *	Licensed under the Apache License, Version 2.0 (the "License");
 *	you may not use this file except in compliance with the License.
 *	You may obtain a copy of the License at
 *	
 *	http://www.apache.org/licenses/LICENSE-2.0
 *	
 *	Unless required by applicable law or agreed to in writing, software
 *	distributed under the License is distributed on an "AS IS" BASIS,
 *	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *	See the License for the specific language governing permissions and
 *	limitations under the License.
 */
package io.continual.util.data;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;

import io.continual.util.time.Clock;

public class HumanReadableHelper
{
	
	private HumanReadableHelper() {
	}
	
	private static final long kMultiplier = 1000;
	private static final long kKilobyte = kMultiplier;
	private static final long kMegabyte = kMultiplier * kKilobyte;
	private static final long kGigabyte = kMultiplier * kMegabyte;
	private static final long kTerabyte = kMultiplier * kGigabyte;
	private static final long kPetabyte = kMultiplier * kTerabyte;
	private static final long kExabyte = kMultiplier * kPetabyte;

	public static String byteCountValue ( long inBytes )
	{
		String result = "" + inBytes + " bytes";
		if ( inBytes > kExabyte )
		{
			double d = inBytes / kExabyte;
			result = "" + d + " EB";
		}
		else if ( inBytes > kPetabyte )
		{
			double d = inBytes / kPetabyte;
			result = "" + d + " PB";
		}
		else if ( inBytes > kTerabyte )
		{
			double d = inBytes / kTerabyte;
			result = "" + d + " TB";
		}
		else if ( inBytes > kGigabyte )
		{
			double d = inBytes / kGigabyte;
			result = "" + d + " GB";
		}
		else if ( inBytes > kMegabyte )
		{
			double d = inBytes / kMegabyte;
			result = "" + d + " MB";
		}
		else if ( inBytes > kKilobyte )
		{
			double d = inBytes / kKilobyte;
			result = "" + d + " kB";
		}
		return result;
	}

	@Deprecated
	public static String memoryValue ( long inBytes )
	{
		return byteCountValue ( inBytes );
	}

	public static List hexDumpLine ( byte[] bytes, int offset, int length )
	{
		return hexDumpLine ( bytes, offset, length, 16 );
	}

	public static List hexDumpLine ( byte[] bytes, int offset, int length, int lineLengthInBytes )
	{
		final LinkedList result = new LinkedList<> ();
		
		while ( offset < length )
		{
			// do the next line...
			final int bytesRemaining = length - offset;
			final int bytesThisLine = offset + Math.min ( bytesRemaining, lineLengthInBytes );

			final StringBuilder hexPart = new StringBuilder ();
			final StringBuilder asciiPart = new StringBuilder ();

			while ( offset < bytesThisLine )
			{
				int ch = bytes[offset];
				final String byteInHex = TypeConvertor.byteToHex ( ch );
				final char byteAsChar = isPrintableForHexDump ( ch ) ? (char) ch : '.';

				hexPart
					.append ( byteInHex )
					.append ( ' ' )
				;
				asciiPart
					.append ( byteAsChar )
				;

				offset++;
			}

			final StringBuilder line = new StringBuilder ()
				.append ( hexPart.toString ()  )
			;
			while ( line.length () < ( lineLengthInBytes * 3 + 4 ) )
			{
				line.append ( ' ' );
			}
			line.append ( asciiPart.toString () );
			result.add ( line.toString () );
		}

		return result;
	}
	
	private static boolean isPrintableForHexDump ( int ch )
	{
		return ch >= 32 && ch <= 126;
	}

	public static final long kMillisecond = 1;
	public static final long kSecond = 1000 * kMillisecond;
	public static final long kMinute = 60 * kSecond;
	public static final long kHour = 60 * kMinute;
	public static final long kDay = 24 * kHour;
	public static final long kWeek = 7 * kDay;
	public static final long kMonth = 30 * kDay;
	public static final long kYear = 365 * kDay;

	/**
	 * Extend a number with 0s to a required width. Intended for decimal-side extension.
	 * @param v a long value
	 * @return a string extended to the required length
	 */
	private static String buildCents ( long v )
	{
		if ( v < 10 )
		{
			return "0" + v;
		}
		else
		{
			String s = "" + v;
			while ( s.length () < 2 )
			{
				s = s + "0";
			}
			return s;
		}
	}

	public static String dollarValue ( double d )
	{
		final boolean negate = d < 0.0;
		if ( negate ) d = d*-1.0;

		long dollars = Math.round ( Math.floor ( d ) );
		long cents = Math.round ( ( d - dollars ) * 100 );
		while ( cents > 99 )
		{
			dollars += 1;
			cents -= 100;
		}

		return (negate?"-":"") + numberValue ( dollars ) + "." + buildCents ( cents );
	}

	public static String roundedDollarValue ( double d )
	{
		return numberValue ( Math.round ( d ) );
	}

	public static String numberValue ( long units )
	{
		final StringBuffer sb = new StringBuffer ();

		final String raw = "" + units;
		final int count = raw.length ();
		final int firstPart = count % 3;
		int printed = 3 - firstPart;
		for ( int i=0; i 0 )
				{
					sb.append ( ',' );
				}
				printed = 0;
			}
			sb.append ( raw.charAt ( i ) );
			printed++;
		}

		return sb.toString ();
	}

	public static String ratioValue ( double d )
	{
		// FIXME: use formatter
		double rounded2 = Math.round ( d * 100 ) / 100.0;
		return "" + rounded2;
	}

	public static String pctValue ( double d )
	{
		// FIXME: use formatter
		final long pct = Math.round ( d * 100 );
		return "" + pct + "%";
	}

	public static String dateValue ( Date d )
	{
		return dateValue ( d, TimeZone.getTimeZone ( "UTC" ) );
	}

	public static String dateValue ( Date d, TimeZone tz )
	{
		final SimpleDateFormat sdf = new SimpleDateFormat ( "yyyy.MM.dd HH:mm:ss z" );
		sdf.setTimeZone ( tz );
		return sdf.format ( d );
	}

	public static String elapsedTimeSince ( Date d )
	{
		if ( d == null ) return "";
		return elapsedTimeSince ( d.getTime () );
	}

	public static String elapsedTimeSince ( long epochMs )
	{
		// return elapsed time with precision that's scaled back as the time grows distant
		return elapsedTimeSince ( epochMs, 1, 2 );
	}

	public static String elapsedTimeSince ( Date d, long smallestUnitInMillis )
	{
		if ( d == null ) return "";
		return elapsedTimeSince ( d.getTime (), smallestUnitInMillis );
	}

	public static String elapsedTimeSince ( long epochMs, long smallestUnitInMillis )
	{
		return elapsedTimeSince ( epochMs, smallestUnitInMillis, -1 );
	}

	public static String elapsedTimeSince ( Date epochMs, long smallestUnitInMillis, int maxLevels )
	{
		return elapsedTimeSince ( epochMs.getTime (), smallestUnitInMillis, maxLevels );
	}

	public static String elapsedTimeSince ( long epochMs, long smallestUnitInMillis, int maxLevels )
	{
		return elapsedTimeBetween ( Clock.now (), epochMs, smallestUnitInMillis, maxLevels );
	}

	public static String elapsedTimeBetween ( long startMs, long endMs )
	{
		return elapsedTimeBetween ( startMs, endMs, 1, 2 );
	}
	
	public static String elapsedTimeBetween ( long startMs, long endMs, long smallestUnitInMillis, int maxLevels )
	{
		final long elapsedMs = startMs - endMs;
		if ( elapsedMs < 0 )
		{
			final String amt = timeValue ( elapsedMs * -1, TimeUnit.MILLISECONDS, smallestUnitInMillis, maxLevels );
			if ( amt == null || amt.length() == 0 ) return "just now";
			return amt + " in the future";
		}
		else
		{
			final String amt = timeValue ( elapsedMs, TimeUnit.MILLISECONDS, smallestUnitInMillis, maxLevels );
			if ( amt == null || amt.length() == 0 ) return "just now";
			return amt + " ago";
		}
	}

	public static String timeValue ( long units, TimeUnit tu, long smallestUnit )
	{
		return timeValue ( units, tu, smallestUnit, -1 );
	}

	public static String timeValue ( long units, TimeUnit tu, long smallestUnit, int maxLevels )
	{
		return timeValue ( new TimeValueContext ( units, tu, smallestUnit, maxLevels ) );
	}

	/**
	 * parse a time duration like "6h" or "10d" into ms
	 * @param duration a time duration string
	 * @return milliseconds
	 */
	public static long parseDuration ( String duration )
	{
		if ( duration.endsWith ( "d" ) || duration.endsWith ( "days" ) )
		{
			final String valueStr = duration.substring ( 0, duration.indexOf ( "d" ) );
			final double value = Double.parseDouble ( valueStr );
			final double asMs = value * (24.0 * 60.0 * 60.0 * 1000.0);
			return Math.round ( asMs );
		}
		else if ( duration.endsWith ( "h" ) || duration.endsWith ( "hr" ) || duration.endsWith ( "hrs" ) )
		{
			final String valueStr = duration.substring ( 0, duration.indexOf ( "h" ) );
			final double value = Double.parseDouble ( valueStr );
			final double asMs = value * (60.0 * 60.0 * 1000.0);
			return Math.round ( asMs );
		}
		else if ( duration.endsWith ( "m" ) || duration.endsWith ( "min" ) || duration.endsWith ( "mins" ) )
		{
			final String valueStr = duration.substring ( 0, duration.indexOf ( "m" ) );
			final double value = Double.parseDouble ( valueStr );
			final double asMs = value * (60.0 * 1000.0);
			return Math.round ( asMs );
		}
		else
		{
			throw new NumberFormatException ( "Can't parse duration [" + duration + "]" );
		}
	}

	/**
	 * Parse date strings as they're typically seen in configuration or input. Note that this isn't tuned to be
	 * especially fast -- use it for occasional interpretation, not high volume transactions.
	 * @param d a date string 
	 * @return a date
	 * @throws ParseException if the date's format is unrecognizable 
	 */
	public static Date parseTypicalDates ( String d ) throws ParseException
	{
		return parseTypicalDates ( d, TimeZone.getTimeZone ( "UTC" ) );
	}

	/**
	 * Parse date strings as they're typically seen in configuration or input. Note that this isn't tuned to be
	 * especially fast -- use it for occasional interpretation, not high volume transactions.
	 * @param d a date string 
	 * @param tz a timezone for the date
	 * @return a date
	 * @throws ParseException if the date's format is unrecognizable 
	 */
	public static Date parseTypicalDates ( String d, TimeZone tz ) throws ParseException
	{
		if ( d.equalsIgnoreCase ( "today" ) || d.equalsIgnoreCase ( "now" ) )
		{
			return new Date ();
		}

		try
		{
			long number = Long.parseLong ( d );
			if ( number > kMsThreshold )
			{
				return new Date ( number );
			}
			else
			{
				return new Date ( number * 1000L );
			}
		}
		catch ( NumberFormatException x )
		{
			// ignore
		}
		
		for ( String sdfFmt : kDateFormats )
		{
			try
			{
				final SimpleDateFormat sdf = new SimpleDateFormat ( sdfFmt );
				sdf.setTimeZone ( tz );
				return sdf.parse ( d );
			}
			catch ( ParseException x )
			{
				// skip it
			}
		}
		throw new ParseException ( "Unrecognized date [" + d +  "].", 0 );
	}

	private static String[] kDateFormats =
	{
		"yyyy-MM-dd",
		"MM/dd/yyyy",
	};
	private static final long kMsThreshold = ( System.currentTimeMillis () / 100L );

	private static long[] kTimeVals = { kYear, kMonth, kWeek, kDay, kHour, kMinute, kSecond, kMillisecond };
	private static String[] kTimeValAbbvsSingle = { "yr", "month", "wk", "day", "hr", "m", "s", "ms" }; 
	private static String[] kTimeValAbbvsPlural = { "yrs", "months", "wks", "days", "hrs", "m", "s", "ms" }; 

	private static class TimeValueContext
	{
		public TimeValueContext ( long units, TimeUnit tu, long smallestUnit, int maxLevels )
		{
			fTimeValueMs = TimeUnit.MILLISECONDS.convert ( units, tu );
			fRemainingMs = fTimeValueMs;
			fSmallestUnit = smallestUnit;
			fMaxLevels = maxLevels;
		}
		public final long fTimeValueMs;
		public long fRemainingMs;
		public long fSmallestUnit;
		public int fMaxLevels;
	};

	private static String timeValue ( TimeValueContext tvc )
	{
		final StringBuffer result = new StringBuffer ();

		int firstUnitIndex = -1;
		for ( int unit = 0; unit < kTimeVals.length; unit++ )
		{
			if ( kTimeVals[unit] < tvc.fSmallestUnit )
			{
				break;
			}
			if ( tvc.fMaxLevels > -1 && firstUnitIndex > -1 && unit >= firstUnitIndex + tvc.fMaxLevels )
			{
				break;
			}
			
			if ( tvc.fRemainingMs >= kTimeVals[unit] )
			{
				// extract the value for this unit
				final long count = tvc.fRemainingMs / kTimeVals[unit];
				tvc.fRemainingMs = tvc.fRemainingMs - ( count * kTimeVals[unit] );

				// update the text string
				if ( firstUnitIndex >= 0 ) result.append ( ", " );
				result
					.append ( count )
					.append ( " " )
					.append ( count == 1 ? kTimeValAbbvsSingle[unit] : kTimeValAbbvsPlural[unit] )
				;

				if ( firstUnitIndex < 0 ) firstUnitIndex = unit;
			}
		}
		return result.toString ();
	}

	/**
	 * Return a string that is a list of items separated by separator and using
	 * the final conjunction. For example, { "A", "B", "C" } --> "A, B, and C"
	 * @param items the input list
	 * @param separator the separator to use in the sequence
	 * @param finalConjunction the final conjunction (e.g. "and") 
	 * @return a list of items
	 */
	public static String listOfItems ( List items, String separator, String finalConjunction )
	{
		final int size = items.size ();
		if ( size < 1 ) return "";

		switch ( size )
		{
			case 1:
				return items.iterator ().next ();

			case 2:
				return items.get ( 0 ) + " " + finalConjunction + " " + items.get ( 1 );

			default:
			{
				final StringBuffer result = new StringBuffer ();
				for ( int i=0; i




© 2015 - 2024 Weber Informatics LLC | Privacy Policy