Displaying file sizes can be challenging, because the units you use can depend on the actual value. And sometimes you want the values based on 1024 bytes-to-the-kilobyte, while others you might want the decimal value generally used to measure storage devices. NSByteCountFormatter can take care of all this for you.
To get the binary value, use:
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
set theString to (current application's NSByteCountFormatter's stringFromByteCount:123456789 countStyle:(current application's NSByteCountFormatterCountStyleDecimal)) as text
--> "123.5 MB"
And for the decimal value:
set theString to (current application's NSByteCountFormatter's stringFromByteCount:123456789 countStyle:(current application's NSByteCountFormatterCountStyleBinary)) as text
--> "117.7 MB"
You can also get more advanced formats, for example:
my formatAsBytes:123456789
--> "123.5 MB (123,456,789 bytes)"
on formatAsBytes:theValue
set theNSByteCountFormatter to current application's NSByteCountFormatter's new()
theNSByteCountFormatter's setIncludesActualByteCount:true
return (theNSByteCountFormatter's stringFromByteCount:theValue) as text
end formatAsBytes:
Sure, just set the NSByteCountFormatter’s allowedUnits property:
use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions
use framework "Foundation"
-- classes, constants, and enums used
property NSByteCountFormatterUseKB : a reference to 2
my formatAsBytes:123456789
--> "123,457 KB (123,456,789 bytes)"
on formatAsBytes:theValue
set theNSByteCountFormatter to current application's NSByteCountFormatter's new()
theNSByteCountFormatter's setIncludesActualByteCount:true
theNSByteCountFormatter's setAllowedUnits:NSByteCountFormatterUseKB
return (theNSByteCountFormatter's stringFromByteCount:theValue) as text
end formatAsBytes:
They are a bit mask. I think MB is 4 and GB is 8. But there is a way to find for yourself: delete the KB in NSByteCountFormatterUseKB and then press Escape to see what completions are offered.
Here’s my refactor of Mark’s script, adding the option for different units.
EDIT: Moved nsUnitsRef to handler.
### Refactor of Script by @alldritt on 2017-12-21 ###
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
set fileSizeNum to missing value
--set fileSizeNum to 123456789
if (fileSizeNum is missing value) then
set filePath to POSIX path of (choose file)
set oFile to info for filePath
set fileSizeNum to size of oFile
end if
set fileSizeMBStr to my convertBytesToString(fileSizeNum, "MB", true)
on convertBytesToString(pSizeNum, pNSUnitsStr, pIncludeBytesBool)
if (pNSUnitsStr = "KB") then
set nsUnits to a reference to 2
else if (pNSUnitsStr = "MB") then
set nsUnits to a reference to 4
else if (pNSUnitsStr = "GB") then
set nsUnits to a reference to 8
else
set nsUnits to a reference to 4 -- default to MB
end if
set theNSByteCountFormatter to current application's NSByteCountFormatter's new()
theNSByteCountFormatter's setIncludesActualByteCount:pIncludeBytesBool
theNSByteCountFormatter's setAllowedUnits:nsUnits
return (theNSByteCountFormatter's stringFromByteCount:pSizeNum) as text
end convertBytesToString
Just in case it’s not obvious, the original version Mark posted will use whichever unit makes sense given the number of bytes. If the number was 1234567890 it would use GB, and for 123456 it would use KB.
So there’s no reason to specify the the unit unless you must have particular units.
Try theNSByteCountFormatter's setAdaptive:false. It defaults to true, and the docs for it say:
The "adaptive" algorithm is platform specific and uses a different number of fraction digits based on the magnitude (in OS X v10.8: 0 fraction digits for bytes and KB; 1 fraction digits for MB; 2 for GB and above). Otherwise the result always tries to show at least three significant digits, introducing fraction digits as necessary.
You may also need to use theNSByteCountFormatter's setZeroPadsFractionDigits.
It looks like this ensure there are the same number of significant digits, so I get this: 0.000123 GB
OR 0.123 MB
set fileSizeNum to 123456
set fileSizeMBStr to my convertBytesToString(fileSizeNum, "GB", false)
-->0.000123 GB
If I use a size of 1234567
I get: 1.23 MB
What I’d like to see for 123456: 0.000 GB 0.123 MB
It doesn’t appear that we need that. I tried both: theNSByteCountFormatter's setZeroPadsFractionDigits:false
AND theNSByteCountFormatter's setZeroPadsFractionDigits:true
If you have a spare $25, you might want to look at Dash. You need to have Xcode installed to download the Objective-C documentation, but then you can search directly from Script Debugger using option-click. It covers a zillion languages, including Javascript and AppleScript – it effectively gives you a searchable version of the ASLG for the latter.
Thanks for providing a simple solution that provides a quick answer, that will be fine for many use cases.
In my use case, I need control over both the units and the format, and that turns out to be a challenge using the ObjC NSByteCountFormatter method.
So, I just wrote a simple solution using math. But it turns out that because native AppleScript is so limited, we have to write handlers and resort to ASObjC to handle simple stuff. So, my method turns out longer than yours. It does more, but requires more lines of code.
property ptyScriptName : "Convert Bytes to Specified Units"
property ptyScriptVer : "1.2" -- use byte conversion method by @NigelGarvey
property ptyScriptDate : "2017-12-23"
property ptyScriptAuthor : "JMichaelTX"
use AppleScript version "2.5" -- El Capitan (10.11) or later
use framework "Foundation" -- this may not be required
use framework "AppKit" -- this may not be required
use scripting additions
## Some Scripts may work with Yosemite, but no guarantees ##
# This script has been tested ONLY in macOS 10.11.6+
---set fileSizeBytes to 995829
--- GET FILE SELECTED in FINDER ---
tell application "Finder" to set finderSelectionList to selection as alias list
if length of finderSelectionList = 0 then error "No files were selected in the Finder!"
set fileAlias to item 1 of finderSelectionList
set fileSizeBytes to size of (get info for fileAlias)
--- CONVERT BYTES TO OTHER UNITS ---
set fileSize to my convertBytes(fileSizeBytes, "MB", "str")
-->995829 returns "0.950 MB"
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
on convertBytes(pSizeBytes, pUnitsStr, pOutputType)
(* VER: 1.1 2017-12-23
-------------------------------------------------------------------------------
PURPOSE: Convert Bytes into other units
PARAMETERS:
• pSizeBytes | num | size in bytes
• pUnitsStr | text | Units to convert to. Must be one of these:
• "KB", "MB", "GB", "TB"
• pOutputType | text | Type of output. Must start with one of these:
• "str", "num"
RETURNS: | text OR num | Size converted to specified units, rounded to 3 places.
• IF pOutputType starts with "str", then returned as text with units label
AUTHOR: JMichaelTX
—————————————————————————————————————————————————————————————————————————————————
*)
--- Better Method THanks to @NigelGarvey ---
set converUnits to "KB MB GB TB"
set convFactor to 1024 ^ ((offset of pUnitsStr in converUnits) div 3 + 1)
set fileSize to pSizeBytes / convFactor
if (pOutputType starts with "Str") then
set fileSize to my formatNumber(fileSize, "#,##0.000;0.000;(#,##0.000)") & " " & pUnitsStr
else
set fileSize to roundThis(fileSize, 3)
end if
return fileSize
end convertBytes
--~~~~~~~~~~~~~~~ END OF handler convertBytes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
on formatNumber(pNumber, pFormatStr)
# REF: Everyday AppleScriptObjC 3rd Edition by Shane Stanley
set theFormatter to current application's NSNumberFormatter's new()
theFormatter's setFormat:pFormatStr
theFormatter's setLocalizesFormat:false
set theResult to theFormatter's stringFromNumber:pNumber
return theResult as text
end formatNumber
on roundThis(n, numDecimals)
# REF: http://macscripter.net/viewtopic.php?id=24415
set x to 10 ^ numDecimals
(((n * x) + 0.5) div 1) / x
end roundThis
set converUnits to "KB MB GB TB"
set convFactor to 1024 ^ ((offset of pUnitsStr in converUnits) div 3 + 1)
This would make it simple to add another parameter specifying the “K” size: 1024 (as in your script) or 1000 (as used for a while now by macOS):
set fileSize to my convertBytes(fileSizeBytes, 1024, "MB", "str")
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
on convertBytes(pSizeBytes, pKSize, pUnitsStr, pOutputType)
set converUnits to "KB MB GB TB"
set convFactor to pKSize ^ ((offset of pUnitsStr in converUnits) div 3 + 1)
-- etc.
Bear in mind that system supplied formatters are designed to perform two other important roles: localization, and consistency of display between applications. So if someone in Japan runs the latter handler in Mark’s post, instead of 123.5 MB (123,456,789 bytes), they will see 123.5 MB(123,456,789バイト).
When you’re writing scripts for your own use, these things may not matter. But when you’re distributing stuff, it can make a difference.
Nigel, yes, you may suggest. I always welcome your suggestions and improvements to my scripts and/or questions.
Thank you so much for taking the time to even read/review my script, and then offering a much better, much more elegant, method.
While I expected your method to be much faster, to my surprise Script Geek reports both methods take the same time: 0.004 sec. (10 run avg).