| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * 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 com.intellij.util.text; |
| |
| import com.intellij.CommonBundle; |
| 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.intellij.util.EnvironmentUtil; |
| import com.sun.jna.Memory; |
| import com.sun.jna.Native; |
| import com.sun.jna.Pointer; |
| 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; |
| |
| public class DateFormatUtil { |
| private static final Logger LOG = Logger.getInstance("com.intellij.util.text.DateFormatUtil"); |
| |
| public static final long SECOND = 1000; |
| public static final long MINUTE = SECOND * 60; |
| public static final long HOUR = MINUTE * 60; |
| public static final long DAY = HOUR * 24; |
| public static final long WEEK = DAY * 7; |
| public static final long MONTH = DAY * 30; |
| public static final long YEAR = DAY * 365; |
| public static final long DAY_FACTOR = 24L * 60 * 60 * 1000; |
| |
| // do not expose this constants - they are very likely to be changed in future |
| private static final SyncDateFormat DATE_FORMAT; |
| private static final SyncDateFormat TIME_FORMAT; |
| private static final SyncDateFormat TIME_WITH_SECONDS_FORMAT; |
| private static final SyncDateFormat DATE_TIME_FORMAT; |
| private static final SyncDateFormat ABOUT_DATE_FORMAT; |
| static { |
| SyncDateFormat[] formats = getDateTimeFormats(); |
| DATE_FORMAT = formats[0]; |
| TIME_FORMAT = formats[1]; |
| TIME_WITH_SECONDS_FORMAT = formats[2]; |
| DATE_TIME_FORMAT = formats[3]; |
| ABOUT_DATE_FORMAT = new SyncDateFormat(DateFormat.getDateInstance(DateFormat.LONG, Locale.US)); |
| } |
| |
| private static final long[] DENOMINATORS = {YEAR, MONTH, WEEK, DAY, HOUR, MINUTE}; |
| private enum Period {YEAR, MONTH, WEEK, DAY, HOUR, MINUTE} |
| private static final Period[] PERIODS = {Period.YEAR, Period.MONTH, Period.WEEK, Period.DAY, Period.HOUR, Period.MINUTE}; |
| |
| private DateFormatUtil() { } |
| |
| public static long getDifferenceInDays(final Date startDate, final Date endDate) { |
| return (endDate.getTime() - startDate.getTime() + DAY_FACTOR - 1000) / DAY_FACTOR; |
| } |
| |
| @NotNull |
| public static SyncDateFormat getDateFormat() { |
| return DATE_FORMAT; |
| } |
| |
| @NotNull |
| public static SyncDateFormat getTimeFormat() { |
| return TIME_FORMAT; |
| } |
| |
| @NotNull |
| public static SyncDateFormat getTimeWithSecondsFormat() { |
| return TIME_WITH_SECONDS_FORMAT; |
| } |
| |
| @NotNull |
| public static SyncDateFormat getDateTimeFormat() { |
| return DATE_TIME_FORMAT; |
| } |
| |
| @NotNull |
| public static String formatTime(@NotNull Date time) { |
| return formatTime(time.getTime()); |
| } |
| |
| @NotNull |
| public static String formatTime(long time) { |
| return getTimeFormat().format(time); |
| } |
| |
| @NotNull |
| public static String formatTimeWithSeconds(@NotNull Date time) { |
| return formatTimeWithSeconds(time.getTime()); |
| } |
| |
| @NotNull |
| public static String formatTimeWithSeconds(long time) { |
| return getTimeWithSecondsFormat().format(time); |
| } |
| |
| @NotNull |
| public static String formatDate(@NotNull Date time) { |
| return formatDate(time.getTime()); |
| } |
| |
| @NotNull |
| public static String formatDate(long time) { |
| return getDateFormat().format(time); |
| } |
| |
| @NotNull |
| public static String formatPrettyDate(@NotNull Date date) { |
| return formatPrettyDate(date.getTime()); |
| } |
| |
| @NotNull |
| public static String formatPrettyDate(long time) { |
| return doFormatPretty(time, false); |
| } |
| |
| @NotNull |
| public static String formatDateTime(Date date) { |
| return formatDateTime(date.getTime()); |
| } |
| |
| @NotNull |
| public static String formatDateTime(long time) { |
| return getDateTimeFormat().format(time); |
| } |
| |
| @NotNull |
| public static String formatPrettyDateTime(Date date) { |
| return formatPrettyDateTime(date.getTime()); |
| } |
| |
| @NotNull |
| public static String formatPrettyDateTime(long time) { |
| return doFormatPretty(time, true); |
| } |
| |
| @NotNull |
| private static String doFormatPretty(long time, boolean formatTime) { |
| long currentTime = Clock.getTime(); |
| |
| Calendar c = Calendar.getInstance(); |
| c.setTimeInMillis(currentTime); |
| |
| int currentYear = c.get(Calendar.YEAR); |
| int currentDayOfYear = c.get(Calendar.DAY_OF_YEAR); |
| |
| c.setTimeInMillis(time); |
| |
| int year = c.get(Calendar.YEAR); |
| int dayOfYear = c.get(Calendar.DAY_OF_YEAR); |
| |
| if (formatTime) { |
| long delta = currentTime - time; |
| if (delta <= HOUR && delta >= 0) { |
| return CommonBundle.message("date.format.minutes.ago", (int)Math.rint(delta / (double)MINUTE)); |
| } |
| } |
| |
| boolean isToday = currentYear == year && currentDayOfYear == dayOfYear; |
| if (isToday) { |
| String result = CommonBundle.message("date.format.today"); |
| if (formatTime) result += " " + TIME_FORMAT.format(time); |
| return result; |
| } |
| |
| boolean isYesterdayOnPreviousYear = |
| (currentYear == year + 1) && currentDayOfYear == 1 && dayOfYear == c.getActualMaximum(Calendar.DAY_OF_YEAR); |
| boolean isYesterday = isYesterdayOnPreviousYear || (currentYear == year && currentDayOfYear == dayOfYear + 1); |
| |
| if (isYesterday) { |
| String result = CommonBundle.message("date.format.yesterday"); |
| if (formatTime) result += " " + TIME_FORMAT.format(time); |
| return result; |
| } |
| |
| return formatTime ? DATE_TIME_FORMAT.format(time) : DATE_FORMAT.format(time); |
| } |
| |
| @NotNull |
| public static String formatDuration(long delta) { |
| StringBuilder buf = new StringBuilder(); |
| for (int i = 0; i < DENOMINATORS.length; i++) { |
| long denominator = DENOMINATORS[i]; |
| int n = (int)(delta / denominator); |
| if (n != 0) { |
| buf.append(composeDurationMessage(PERIODS[i], n)); |
| buf.append(' '); |
| delta = delta % denominator; |
| } |
| } |
| |
| if (buf.length() == 0) return CommonBundle.message("date.format.less.than.a.minute"); |
| return buf.toString().trim(); |
| } |
| |
| private static String composeDurationMessage(final Period period, final int n) { |
| switch (period) { |
| case DAY: |
| return CommonBundle.message("date.format.n.days", n); |
| case MINUTE: |
| return CommonBundle.message("date.format.n.minutes", n); |
| case HOUR: |
| return CommonBundle.message("date.format.n.hours", n); |
| case MONTH: |
| return CommonBundle.message("date.format.n.months", n); |
| case WEEK: |
| return CommonBundle.message("date.format.n.weeks", n); |
| default: |
| return CommonBundle.message("date.format.n.years", n); |
| } |
| } |
| |
| @NotNull |
| public static String formatFrequency(long time) { |
| return CommonBundle.message("date.frequency", formatBetweenDates(time, 0)); |
| } |
| |
| @NotNull |
| public static String formatBetweenDates(long d1, long d2) { |
| long delta = Math.abs(d1 - d2); |
| if (delta == 0) return CommonBundle.message("date.format.right.now"); |
| |
| int n = -1; |
| int i; |
| for (i = 0; i < DENOMINATORS.length; i++) { |
| long denominator = DENOMINATORS[i]; |
| if (delta >= denominator) { |
| n = (int)(delta / denominator); |
| break; |
| } |
| } |
| |
| if (d2 > d1) { |
| if (n <= 0) { |
| return CommonBundle.message("date.format.a.few.moments.ago"); |
| } |
| else { |
| return someTimeAgoMessage(PERIODS[i], n); |
| } |
| } |
| else if (d2 < d1) { |
| if (n <= 0) { |
| return CommonBundle.message("date.format.in.a.few.moments"); |
| } |
| else { |
| return composeInSomeTimeMessage(PERIODS[i], n); |
| } |
| } |
| |
| return ""; |
| } |
| |
| @NotNull |
| public static String formatAboutDialogDate(@NotNull Date date) { |
| return ABOUT_DATE_FORMAT.format(date); |
| } |
| |
| // helpers |
| |
| private static String someTimeAgoMessage(final Period period, final int n) { |
| switch (period) { |
| case DAY: |
| return CommonBundle.message("date.format.n.days.ago", n); |
| case MINUTE: |
| return CommonBundle.message("date.format.n.minutes.ago", n); |
| case HOUR: |
| return CommonBundle.message("date.format.n.hours.ago", n); |
| case MONTH: |
| return CommonBundle.message("date.format.n.months.ago", n); |
| case WEEK: |
| return CommonBundle.message("date.format.n.weeks.ago", n); |
| default: |
| return CommonBundle.message("date.format.n.years.ago", n); |
| } |
| } |
| |
| private static String composeInSomeTimeMessage(final Period period, final int n) { |
| switch (period) { |
| case DAY: |
| return CommonBundle.message("date.format.in.n.days", n); |
| case MINUTE: |
| return CommonBundle.message("date.format.in.n.minutes", n); |
| case HOUR: |
| return CommonBundle.message("date.format.in.n.hours", n); |
| case MONTH: |
| return CommonBundle.message("date.format.in.n.months", n); |
| case WEEK: |
| return CommonBundle.message("date.format.in.n.weeks", n); |
| default: |
| return CommonBundle.message("date.format.in.n.years", n); |
| } |
| } |
| |
| private static SyncDateFormat[] getDateTimeFormats() { |
| DateFormat[] formats = new DateFormat[4]; |
| |
| boolean loaded = false; |
| try { |
| if (SystemInfo.isWin7OrNewer) { |
| loaded = getWindowsFormats(formats); |
| } |
| else if (SystemInfo.isMac) { |
| loaded = getMacFormats(formats); |
| } |
| else if (SystemInfo.isUnix) { |
| loaded = getUnixFormats(formats); |
| } |
| } |
| catch (Throwable t) { |
| LOG.error(t); |
| } |
| |
| 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); |
| } |
| |
| SyncDateFormat[] synced = new SyncDateFormat[4]; |
| for (int i = 0; i < formats.length; i++) { |
| synced[i] = new SyncDateFormat(formats[i]); |
| } |
| 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; |
| |
| ID autoReleasePool = Foundation.invoke("NSAutoreleasePool", "new"); |
| 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; |
| } |
| finally { |
| Foundation.invoke(autoReleasePool, Foundation.createSelector("release")); |
| } |
| } |
| |
| 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 new SimpleDateFormat(format.trim()); |
| } |
| |
| private static boolean getUnixFormats(DateFormat[] formats) { |
| String localeStr = EnvironmentUtil.getValue("LC_TIME"); |
| if (localeStr == null) return false; |
| |
| localeStr = localeStr.trim(); |
| int p = localeStr.indexOf('.'); |
| if (p > 0) localeStr = localeStr.substring(0, p); |
| p = localeStr.indexOf('@'); |
| if (p > 0) localeStr = localeStr.substring(0, p); |
| |
| Locale locale; |
| p = localeStr.indexOf('_'); |
| if (p < 0) { |
| locale = new Locale(localeStr); |
| } |
| else { |
| 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; |
| } |
| |
| @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 GetLastError(); |
| } |
| |
| private static boolean getWindowsFormats(DateFormat[] formats) { |
| Kernel32 kernel32 = (Kernel32)Native.loadLibrary("Kernel32", Kernel32.class); |
| int dataSize = 128, rv; |
| Memory data = new Memory(dataSize); |
| |
| rv = kernel32.GetLocaleInfoEx(Kernel32.LOCALE_NAME_USER_DEFAULT, Kernel32.LOCALE_SSHORTDATE, data, dataSize); |
| assert rv > 1 : kernel32.GetLastError(); |
| String shortDate = new String(data.getCharArray(0, rv - 1)); |
| |
| rv = kernel32.GetLocaleInfoEx(Kernel32.LOCALE_NAME_USER_DEFAULT, Kernel32.LOCALE_SSHORTTIME, data, dataSize); |
| assert rv > 1 : kernel32.GetLastError(); |
| String shortTime = StringUtil.replace(new String(data.getCharArray(0, rv - 1)), "tt", "a"); |
| |
| rv = kernel32.GetLocaleInfoEx(Kernel32.LOCALE_NAME_USER_DEFAULT, Kernel32.LOCALE_STIMEFORMAT, data, dataSize); |
| assert rv > 1 : kernel32.GetLastError(); |
| String mediumTime = StringUtil.replace(new String(data.getCharArray(0, rv - 1)), "tt", "a"); |
| |
| formats[0] = new SimpleDateFormat(shortDate); |
| formats[1] = new SimpleDateFormat(shortTime); |
| formats[2] = new SimpleDateFormat(mediumTime); |
| formats[3] = new SimpleDateFormat(shortDate + " " + shortTime); |
| |
| return true; |
| } |
| } |