[platform] improves system date format loading

(migrates macOS code to CoreFoundation; drops character copying from Windows code)
This commit is contained in:
Roman Shevchenko
2018-06-26 13:59:02 +03:00
parent 1dbf8ec416
commit 9e632c7b1c
@@ -7,20 +7,13 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Clock;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.mac.foundation.Foundation;
import com.intellij.ui.mac.foundation.ID;
import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.*;
import com.sun.jna.win32.StdCallLibrary;
import org.jetbrains.annotations.NotNull;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import java.util.*;
public class DateFormatUtil {
private static final Logger LOG = Logger.getInstance("com.intellij.util.text.DateFormatUtil");
@@ -279,7 +272,6 @@ public class DateFormatUtil {
//<editor-fold desc="Helpers.">
@SuppressWarnings("Duplicates")
private static String someTimeAgoMessage(final Period period, final int n) {
switch (period) {
@@ -317,35 +309,32 @@ public class DateFormatUtil {
}
private static SyncDateFormat[] getDateTimeFormats() {
DateFormat[] formats = new DateFormat[4];
boolean loaded = false;
if (JnaLoader.isLoaded() || SystemInfo.isXWindow) {
try {
if (SystemInfo.isWin7OrNewer) {
loaded = getWindowsFormats(formats);
}
else if (SystemInfo.isMac) {
loaded = getMacFormats(formats);
}
else if (SystemInfo.isUnix) {
loaded = getUnixFormats(formats);
}
DateFormat[] formats = null;
try {
if (SystemInfo.isMac && JnaLoader.isLoaded()) {
formats = getMacFormats();
}
catch (Throwable t) {
LOG.error(t);
else if (SystemInfo.isUnix) {
formats = getUnixFormats();
}
else if (SystemInfo.isWin7OrNewer && JnaLoader.isLoaded() ) {
formats = getWindowsFormats();
}
}
if (!loaded) {
formats[0] = DateFormat.getDateInstance(DateFormat.SHORT);
formats[1] = DateFormat.getTimeInstance(DateFormat.SHORT);
formats[2] = DateFormat.getTimeInstance(DateFormat.MEDIUM);
formats[3] = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
catch (Throwable t) {
LOG.error(t);
}
if (formats == null) {
formats = new DateFormat[]{
DateFormat.getDateInstance(DateFormat.SHORT),
DateFormat.getTimeInstance(DateFormat.SHORT),
DateFormat.getTimeInstance(DateFormat.MEDIUM),
DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT)
};
}
if (LOG.isTraceEnabled()) {
LOG.trace("formats (loaded=" + loaded + " JNA=" + JnaLoader.isLoaded() + ")");
LOG.trace("formats (OS=" + SystemInfo.OS_NAME + " JNA=" + JnaLoader.isLoaded() + ")");
for (DateFormat format: formats) {
LOG.trace("'" + (format instanceof SimpleDateFormat ? ((SimpleDateFormat)format).toPattern() : format.toString()) + "'");
}
@@ -358,40 +347,63 @@ public class DateFormatUtil {
return synced;
}
private static boolean getMacFormats(DateFormat[] formats) {
final int MacFormatterNoStyle = 0;
final int MacFormatterShortStyle = 1;
final int MacFormatterMediumStyle = 2;
final int MacFormatterBehavior_10_4 = 1040;
private interface CF extends Library {
long kCFDateFormatterNoStyle = 0;
long kCFDateFormatterShortStyle = 1;
long kCFDateFormatterMediumStyle = 2;
ID autoReleasePool = Foundation.invoke("NSAutoreleasePool", "new");
class CFRange extends Structure implements Structure.ByValue {
@Override
protected List<String> getFieldOrder() {
return Arrays.asList("location", "length");
}
public long location;
public long length;
public CFRange(long location, long length) {
this.location = location;
this.length = length;
}
}
Pointer CFDateFormatterCreate(Pointer allocator, Pointer locale, long dateStyle, long timeStyle);
Pointer CFDateFormatterGetFormat(Pointer formatter);
long CFStringGetLength(Pointer str);
void CFStringGetCharacters(Pointer str, CFRange range, char[] buffer);
void CFRelease(Pointer p);
}
// platform-specific patterns: http://www.unicode.org/reports/tr35/tr35-31/tr35-dates.html#Date_Format_Patterns
private static DateFormat[] getMacFormats() {
CF cf = Native.loadLibrary("CoreFoundation", CF.class);
return new DateFormat[]{
getMacFormat(cf, CF.kCFDateFormatterShortStyle, CF.kCFDateFormatterNoStyle), // short date
getMacFormat(cf, CF.kCFDateFormatterNoStyle, CF.kCFDateFormatterShortStyle), // short time
getMacFormat(cf, CF.kCFDateFormatterNoStyle, CF.kCFDateFormatterMediumStyle), // medium time
getMacFormat(cf, CF.kCFDateFormatterShortStyle, CF.kCFDateFormatterShortStyle) // short date/time
};
}
private static DateFormat getMacFormat(CF cf, long dateStyle, long timeStyle) {
Pointer formatter = cf.CFDateFormatterCreate(null, null, dateStyle, timeStyle);
if (formatter == null) throw new IllegalStateException("CFDateFormatterCreate: null");
try {
ID dateFormatter = Foundation.invoke("NSDateFormatter", "new");
Foundation.invoke(dateFormatter, Foundation.createSelector("setFormatterBehavior:"), MacFormatterBehavior_10_4);
formats[0] = invokeFormatter(dateFormatter, MacFormatterNoStyle, MacFormatterShortStyle); // short date
formats[1] = invokeFormatter(dateFormatter, MacFormatterShortStyle, MacFormatterNoStyle); // short time
formats[2] = invokeFormatter(dateFormatter, MacFormatterMediumStyle, MacFormatterNoStyle); // medium time
formats[3] = invokeFormatter(dateFormatter, MacFormatterShortStyle, MacFormatterShortStyle); // short date/time
return true;
Pointer format = cf.CFDateFormatterGetFormat(formatter);
int length = (int)cf.CFStringGetLength(format);
char[] buffer = new char[length];
cf.CFStringGetCharacters(format, new CF.CFRange(0, length), buffer);
return formatFromString(new String(buffer));
}
finally {
Foundation.invoke(autoReleasePool, Foundation.createSelector("release"));
cf.CFRelease(formatter);
}
}
private static DateFormat invokeFormatter(ID dateFormatter, int timeStyle, int dateStyle) {
Foundation.invoke(dateFormatter, Foundation.createSelector("setTimeStyle:"), timeStyle);
Foundation.invoke(dateFormatter, Foundation.createSelector("setDateStyle:"), dateStyle);
String format = Foundation.toStringViaUTF8(Foundation.invoke(dateFormatter, Foundation.createSelector("dateFormat")));
assert format != null;
return formatFromString(format);
}
private static boolean getUnixFormats(DateFormat[] formats) {
private static DateFormat[] getUnixFormats() {
String localeStr = System.getenv("LC_TIME");
if (localeStr == null) return false;
if (LOG.isTraceEnabled()) LOG.trace("LC_TIME=" + localeStr);
if (localeStr == null) return null;
localeStr = localeStr.trim();
int p = localeStr.indexOf('.');
@@ -408,49 +420,47 @@ public class DateFormatUtil {
locale = new Locale(localeStr.substring(0, p), localeStr.substring(p + 1));
}
formats[0] = DateFormat.getDateInstance(DateFormat.SHORT, locale);
formats[1] = DateFormat.getTimeInstance(DateFormat.SHORT, locale);
formats[2] = DateFormat.getTimeInstance(DateFormat.MEDIUM, locale);
formats[3] = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale);
return true;
return new DateFormat[]{
DateFormat.getDateInstance(DateFormat.SHORT, locale),
DateFormat.getTimeInstance(DateFormat.SHORT, locale),
DateFormat.getTimeInstance(DateFormat.MEDIUM, locale),
DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale)
};
}
@SuppressWarnings("SpellCheckingInspection")
private interface Kernel32 extends StdCallLibrary {
String LOCALE_NAME_USER_DEFAULT = null;
int LOCALE_SSHORTDATE = 0x0000001F;
int LOCALE_SSHORTTIME = 0x00000079;
int LOCALE_STIMEFORMAT = 0x00001003;
int GetLocaleInfoEx(String localeName, int lcType, Pointer lcData, int dataSize);
int GetLocaleInfoEx(String localeName, int lcType, char[] lcData, int dataSize);
int GetLastError();
}
private static boolean getWindowsFormats(DateFormat[] formats) {
private static DateFormat[] getWindowsFormats() {
Kernel32 kernel32 = Native.loadLibrary("Kernel32", Kernel32.class);
int dataSize = 128, rv;
Memory data = new Memory(dataSize);
int bufferSize = 128, rv;
char[] buffer = new char[bufferSize];
rv = kernel32.GetLocaleInfoEx(Kernel32.LOCALE_NAME_USER_DEFAULT, Kernel32.LOCALE_SSHORTDATE, data, dataSize);
assert rv > 1 : kernel32.GetLastError();
String shortDate = fixWindowsFormat(new String(data.getCharArray(0, rv - 1)));
rv = kernel32.GetLocaleInfoEx(null, Kernel32.LOCALE_SSHORTDATE, buffer, bufferSize);
if (rv < 2) throw new IllegalStateException("GetLocaleInfoEx: " + kernel32.GetLastError());
String shortDate = fixWindowsFormat(new String(buffer, 0, rv - 1));
rv = kernel32.GetLocaleInfoEx(Kernel32.LOCALE_NAME_USER_DEFAULT, Kernel32.LOCALE_SSHORTTIME, data, dataSize);
assert rv > 1 : kernel32.GetLastError();
String shortTime = fixWindowsFormat(new String(data.getCharArray(0, rv - 1)));
rv = kernel32.GetLocaleInfoEx(null, Kernel32.LOCALE_SSHORTTIME, buffer, bufferSize);
if (rv < 2) throw new IllegalStateException("GetLocaleInfoEx: " + kernel32.GetLastError());
String shortTime = fixWindowsFormat(new String(buffer, 0, rv - 1));
rv = kernel32.GetLocaleInfoEx(Kernel32.LOCALE_NAME_USER_DEFAULT, Kernel32.LOCALE_STIMEFORMAT, data, dataSize);
assert rv > 1 : kernel32.GetLastError();
String mediumTime = fixWindowsFormat(new String(data.getCharArray(0, rv - 1)));
rv = kernel32.GetLocaleInfoEx(null, Kernel32.LOCALE_STIMEFORMAT, buffer, bufferSize);
if (rv < 2) throw new IllegalStateException("GetLocaleInfoEx: " + kernel32.GetLastError());
String mediumTime = fixWindowsFormat(new String(buffer, 0, rv - 1));
formats[0] = formatFromString(shortDate);
formats[1] = formatFromString(shortTime);
formats[2] = formatFromString(mediumTime);
formats[3] = formatFromString(shortDate + " " + shortTime);
return true;
return new DateFormat[]{
formatFromString(shortDate),
formatFromString(shortTime),
formatFromString(mediumTime),
formatFromString(shortDate + " " + shortTime)
};
}
private static String fixWindowsFormat(String format) {