You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

DateTimeService.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. /*
  2. @VaadinApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.client;
  5. import java.util.Date;
  6. import com.google.gwt.i18n.client.DateTimeFormat;
  7. import com.google.gwt.i18n.client.LocaleInfo;
  8. import com.vaadin.terminal.gwt.client.ui.datefield.VDateField;
  9. /**
  10. * This class provides date/time parsing services to all components on the
  11. * client side.
  12. *
  13. * @author Vaadin Ltd.
  14. *
  15. */
  16. @SuppressWarnings("deprecation")
  17. public class DateTimeService {
  18. private String currentLocale;
  19. private static int[] maxDaysInMonth = { 31, 28, 31, 30, 31, 30, 31, 31, 30,
  20. 31, 30, 31 };
  21. /**
  22. * Creates a new date time service with the application default locale.
  23. */
  24. public DateTimeService() {
  25. currentLocale = LocaleService.getDefaultLocale();
  26. }
  27. /**
  28. * Creates a new date time service with a given locale.
  29. *
  30. * @param locale
  31. * e.g. fi, en etc.
  32. * @throws LocaleNotLoadedException
  33. */
  34. public DateTimeService(String locale) throws LocaleNotLoadedException {
  35. setLocale(locale);
  36. }
  37. public void setLocale(String locale) throws LocaleNotLoadedException {
  38. if (LocaleService.getAvailableLocales().contains(locale)) {
  39. currentLocale = locale;
  40. } else {
  41. throw new LocaleNotLoadedException(locale);
  42. }
  43. }
  44. public String getLocale() {
  45. return currentLocale;
  46. }
  47. public String getMonth(int month) {
  48. try {
  49. return LocaleService.getMonthNames(currentLocale)[month];
  50. } catch (final LocaleNotLoadedException e) {
  51. VConsole.error(e);
  52. return null;
  53. }
  54. }
  55. public String getShortMonth(int month) {
  56. try {
  57. return LocaleService.getShortMonthNames(currentLocale)[month];
  58. } catch (final LocaleNotLoadedException e) {
  59. VConsole.error(e);
  60. return null;
  61. }
  62. }
  63. public String getDay(int day) {
  64. try {
  65. return LocaleService.getDayNames(currentLocale)[day];
  66. } catch (final LocaleNotLoadedException e) {
  67. VConsole.error(e);
  68. return null;
  69. }
  70. }
  71. public String getShortDay(int day) {
  72. try {
  73. return LocaleService.getShortDayNames(currentLocale)[day];
  74. } catch (final LocaleNotLoadedException e) {
  75. VConsole.error(e);
  76. return null;
  77. }
  78. }
  79. public int getFirstDayOfWeek() {
  80. try {
  81. return LocaleService.getFirstDayOfWeek(currentLocale);
  82. } catch (final LocaleNotLoadedException e) {
  83. VConsole.error(e);
  84. return 0;
  85. }
  86. }
  87. public boolean isTwelveHourClock() {
  88. try {
  89. return LocaleService.isTwelveHourClock(currentLocale);
  90. } catch (final LocaleNotLoadedException e) {
  91. VConsole.error(e);
  92. return false;
  93. }
  94. }
  95. public String getClockDelimeter() {
  96. try {
  97. return LocaleService.getClockDelimiter(currentLocale);
  98. } catch (final LocaleNotLoadedException e) {
  99. VConsole.error(e);
  100. return ":";
  101. }
  102. }
  103. private static final String[] DEFAULT_AMPM_STRINGS = { "AM", "PM" };
  104. public String[] getAmPmStrings() {
  105. try {
  106. return LocaleService.getAmPmStrings(currentLocale);
  107. } catch (final LocaleNotLoadedException e) {
  108. // TODO can this practically even happen? Should die instead?
  109. VConsole.error("Locale not loaded, using fallback : AM/PM");
  110. VConsole.error(e);
  111. return DEFAULT_AMPM_STRINGS;
  112. }
  113. }
  114. public int getStartWeekDay(Date date) {
  115. final Date dateForFirstOfThisMonth = new Date(date.getYear(),
  116. date.getMonth(), 1);
  117. int firstDay;
  118. try {
  119. firstDay = LocaleService.getFirstDayOfWeek(currentLocale);
  120. } catch (final LocaleNotLoadedException e) {
  121. VConsole.error("Locale not loaded, using fallback 0");
  122. VConsole.error(e);
  123. firstDay = 0;
  124. }
  125. int start = dateForFirstOfThisMonth.getDay() - firstDay;
  126. if (start < 0) {
  127. start = 6;
  128. }
  129. return start;
  130. }
  131. public static void setMilliseconds(Date date, int ms) {
  132. date.setTime(date.getTime() / 1000 * 1000 + ms);
  133. }
  134. public static int getMilliseconds(Date date) {
  135. if (date == null) {
  136. return 0;
  137. }
  138. return (int) (date.getTime() - date.getTime() / 1000 * 1000);
  139. }
  140. public static int getNumberOfDaysInMonth(Date date) {
  141. final int month = date.getMonth();
  142. if (month == 1 && true == isLeapYear(date)) {
  143. return 29;
  144. }
  145. return maxDaysInMonth[month];
  146. }
  147. public static boolean isLeapYear(Date date) {
  148. // Instantiate the date for 1st March of that year
  149. final Date firstMarch = new Date(date.getYear(), 2, 1);
  150. // Go back 1 day
  151. final long firstMarchTime = firstMarch.getTime();
  152. final long lastDayTimeFeb = firstMarchTime - (24 * 60 * 60 * 1000); // NUM_MILLISECS_A_DAY
  153. // Instantiate new Date with this time
  154. final Date febLastDay = new Date(lastDayTimeFeb);
  155. // Check for date in this new instance
  156. return (29 == febLastDay.getDate()) ? true : false;
  157. }
  158. public static boolean isSameDay(Date d1, Date d2) {
  159. return (getDayInt(d1) == getDayInt(d2));
  160. }
  161. public static boolean isInRange(Date date, Date rangeStart, Date rangeEnd,
  162. int resolution) {
  163. Date s;
  164. Date e;
  165. if (rangeStart.after(rangeEnd)) {
  166. s = rangeEnd;
  167. e = rangeStart;
  168. } else {
  169. e = rangeEnd;
  170. s = rangeStart;
  171. }
  172. long start = s.getYear() * 10000000000l;
  173. long end = e.getYear() * 10000000000l;
  174. long target = date.getYear() * 10000000000l;
  175. if (resolution == VDateField.RESOLUTION_YEAR) {
  176. return (start <= target && end >= target);
  177. }
  178. start += s.getMonth() * 100000000l;
  179. end += e.getMonth() * 100000000l;
  180. target += date.getMonth() * 100000000l;
  181. if (resolution == VDateField.RESOLUTION_MONTH) {
  182. return (start <= target && end >= target);
  183. }
  184. start += s.getDate() * 1000000l;
  185. end += e.getDate() * 1000000l;
  186. target += date.getDate() * 1000000l;
  187. if (resolution == VDateField.RESOLUTION_DAY) {
  188. return (start <= target && end >= target);
  189. }
  190. start += s.getHours() * 10000l;
  191. end += e.getHours() * 10000l;
  192. target += date.getHours() * 10000l;
  193. if (resolution == VDateField.RESOLUTION_HOUR) {
  194. return (start <= target && end >= target);
  195. }
  196. start += s.getMinutes() * 100l;
  197. end += e.getMinutes() * 100l;
  198. target += date.getMinutes() * 100l;
  199. if (resolution == VDateField.RESOLUTION_MIN) {
  200. return (start <= target && end >= target);
  201. }
  202. start += s.getSeconds();
  203. end += e.getSeconds();
  204. target += date.getSeconds();
  205. return (start <= target && end >= target);
  206. }
  207. private static int getDayInt(Date date) {
  208. final int y = date.getYear();
  209. final int m = date.getMonth();
  210. final int d = date.getDate();
  211. return ((y + 1900) * 10000 + m * 100 + d) * 1000000000;
  212. }
  213. /**
  214. * Returns the ISO-8601 week number of the given date.
  215. *
  216. * @param date
  217. * The date for which the week number should be resolved
  218. * @return The ISO-8601 week number for {@literal date}
  219. */
  220. public static int getISOWeekNumber(Date date) {
  221. final long MILLISECONDS_PER_DAY = 24 * 3600 * 1000;
  222. int dayOfWeek = date.getDay(); // 0 == sunday
  223. // ISO 8601 use weeks that start on monday so we use
  224. // mon=1,tue=2,...sun=7;
  225. if (dayOfWeek == 0) {
  226. dayOfWeek = 7;
  227. }
  228. // Find nearest thursday (defines the week in ISO 8601). The week number
  229. // for the nearest thursday is the same as for the target date.
  230. int nearestThursdayDiff = 4 - dayOfWeek; // 4 is thursday
  231. Date nearestThursday = new Date(date.getTime() + nearestThursdayDiff
  232. * MILLISECONDS_PER_DAY);
  233. Date firstOfJanuary = new Date(nearestThursday.getYear(), 0, 1);
  234. long timeDiff = nearestThursday.getTime() - firstOfJanuary.getTime();
  235. int daysSinceFirstOfJanuary = (int) (timeDiff / MILLISECONDS_PER_DAY);
  236. int weekNumber = (daysSinceFirstOfJanuary) / 7 + 1;
  237. return weekNumber;
  238. }
  239. /**
  240. * Check if format contains the month name. If it does we manually convert
  241. * it to the month name since DateTimeFormat.format always uses the current
  242. * locale and will replace the month name wrong if current locale is
  243. * different from the locale set for the DateField.
  244. *
  245. * MMMM is converted into long month name, MMM is converted into short month
  246. * name. '' are added around the name to avoid that DateTimeFormat parses
  247. * the month name as a pattern.
  248. *
  249. * @param date
  250. * The date to convert
  251. * @param formatStr
  252. * The format string that might contain MMM or MMMM
  253. * @param dateTimeService
  254. * Reference to the Vaadin DateTimeService
  255. * @return
  256. */
  257. public String formatDate(Date date, String formatStr) {
  258. /*
  259. * Format month names separately when locale for the DateTimeService is
  260. * not the same as the browser locale
  261. */
  262. formatStr = formatMonthNames(date, formatStr);
  263. // Format uses the browser locale
  264. DateTimeFormat format = DateTimeFormat.getFormat(formatStr);
  265. String result = format.format(date);
  266. return result;
  267. }
  268. private String formatMonthNames(Date date, String formatStr) {
  269. if (formatStr.contains("MMMM")) {
  270. String monthName = getMonth(date.getMonth());
  271. if (monthName != null) {
  272. /*
  273. * Replace 4 or more M:s with the quoted month name. Also
  274. * concatenate generated string with any other string prepending
  275. * or following the MMMM pattern, i.e. 'MMMM'ta ' becomes
  276. * 'MONTHta ' and not 'MONTH''ta ', 'ab'MMMM becomes 'abMONTH',
  277. * 'x'MMMM'y' becomes 'xMONTHy'.
  278. */
  279. formatStr = formatStr.replaceAll("'([M]{4,})'", monthName);
  280. formatStr = formatStr.replaceAll("([M]{4,})'", "'" + monthName);
  281. formatStr = formatStr.replaceAll("'([M]{4,})", monthName + "'");
  282. formatStr = formatStr.replaceAll("[M]{4,}", "'" + monthName
  283. + "'");
  284. }
  285. }
  286. if (formatStr.contains("MMM")) {
  287. String monthName = getShortMonth(date.getMonth());
  288. if (monthName != null) {
  289. /*
  290. * Replace 3 or more M:s with the quoted month name. Also
  291. * concatenate generated string with any other string prepending
  292. * or following the MMM pattern, i.e. 'MMM'ta ' becomes 'MONTHta
  293. * ' and not 'MONTH''ta ', 'ab'MMM becomes 'abMONTH', 'x'MMM'y'
  294. * becomes 'xMONTHy'.
  295. */
  296. formatStr = formatStr.replaceAll("'([M]{3,})'", monthName);
  297. formatStr = formatStr.replaceAll("([M]{3,})'", "'" + monthName);
  298. formatStr = formatStr.replaceAll("'([M]{3,})", monthName + "'");
  299. formatStr = formatStr.replaceAll("[M]{3,}", "'" + monthName
  300. + "'");
  301. }
  302. }
  303. return formatStr;
  304. }
  305. /**
  306. * Replaces month names in the entered date with the name in the current
  307. * browser locale.
  308. *
  309. * @param enteredDate
  310. * Date string e.g. "5 May 2010"
  311. * @param formatString
  312. * Format string e.g. "d M yyyy"
  313. * @return The date string where the month names have been replaced by the
  314. * browser locale version
  315. */
  316. private String parseMonthName(String enteredDate, String formatString) {
  317. LocaleInfo browserLocale = LocaleInfo.getCurrentLocale();
  318. if (browserLocale.getLocaleName().equals(getLocale())) {
  319. // No conversion needs to be done when locales match
  320. return enteredDate;
  321. }
  322. String[] browserMonthNames = browserLocale.getDateTimeConstants()
  323. .months();
  324. String[] browserShortMonthNames = browserLocale.getDateTimeConstants()
  325. .shortMonths();
  326. if (formatString.contains("MMMM")) {
  327. // Full month name
  328. for (int i = 0; i < 12; i++) {
  329. enteredDate = enteredDate.replaceAll(getMonth(i),
  330. browserMonthNames[i]);
  331. }
  332. }
  333. if (formatString.contains("MMM")) {
  334. // Short month name
  335. for (int i = 0; i < 12; i++) {
  336. enteredDate = enteredDate.replaceAll(getShortMonth(i),
  337. browserShortMonthNames[i]);
  338. }
  339. }
  340. return enteredDate;
  341. }
  342. /**
  343. * Parses the given date string using the given format string and the locale
  344. * set in this DateTimeService instance.
  345. *
  346. * @param dateString
  347. * Date string e.g. "1 February 2010"
  348. * @param formatString
  349. * Format string e.g. "d MMMM yyyy"
  350. * @param lenient
  351. * true to use lenient parsing, false to use strict parsing
  352. * @return A Date object representing the dateString. Never returns null.
  353. * @throws IllegalArgumentException
  354. * if the parsing fails
  355. *
  356. */
  357. public Date parseDate(String dateString, String formatString,
  358. boolean lenient) throws IllegalArgumentException {
  359. /* DateTimeFormat uses the browser's locale */
  360. DateTimeFormat format = DateTimeFormat.getFormat(formatString);
  361. /*
  362. * Parse month names separately when locale for the DateTimeService is
  363. * not the same as the browser locale
  364. */
  365. dateString = parseMonthName(dateString, formatString);
  366. Date date;
  367. if (lenient) {
  368. date = format.parse(dateString);
  369. } else {
  370. date = format.parseStrict(dateString);
  371. }
  372. // Some version of Firefox sets the timestamp to 0 if parsing fails.
  373. if (date != null && date.getTime() == 0) {
  374. throw new IllegalArgumentException("Parsing of '" + dateString
  375. + "' failed");
  376. }
  377. return date;
  378. }
  379. }