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 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672
  1. /*
  2. * Copyright 2000-2021 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.client;
  17. import java.util.Date;
  18. import java.util.logging.Level;
  19. import java.util.logging.Logger;
  20. import com.google.gwt.i18n.client.LocaleInfo;
  21. import com.google.gwt.i18n.client.TimeZone;
  22. import com.google.gwt.i18n.shared.DateTimeFormat;
  23. import com.vaadin.shared.ui.datefield.DateResolution;
  24. /**
  25. * This class provides date/time parsing services to all components on the
  26. * client side.
  27. *
  28. * @author Vaadin Ltd.
  29. *
  30. */
  31. @SuppressWarnings("deprecation")
  32. public class DateTimeService {
  33. private String locale;
  34. private static int[] maxDaysInMonth = { 31, 28, 31, 30, 31, 30, 31, 31, 30,
  35. 31, 30, 31 };
  36. private int[] yearDays = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273,
  37. 304, 334 };
  38. private int[] leapYearDays = { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274,
  39. 305, 335};
  40. private static final long MILLISECONDS_PER_DAY = 24 * 3600 * 1000;
  41. /**
  42. * Creates a new date time service with the application default locale.
  43. */
  44. public DateTimeService() {
  45. locale = LocaleService.getDefaultLocale();
  46. }
  47. /**
  48. * Creates a new date time service with a given locale.
  49. *
  50. * @param locale
  51. * e.g. {@code fi}, {@code en}, etc.
  52. * @throws LocaleNotLoadedException
  53. */
  54. public DateTimeService(String locale) throws LocaleNotLoadedException {
  55. setLocale(locale);
  56. }
  57. /**
  58. * Utility method to format positive int as zero-padded two-digits number.
  59. *
  60. * @param i
  61. * the value
  62. * @return "00".."99"
  63. * @since 8.4
  64. */
  65. public static String asTwoDigits(int i) {
  66. return (i < 10 ? "0" : "") + i;
  67. }
  68. public void setLocale(String locale) throws LocaleNotLoadedException {
  69. if (!LocaleService.getAvailableLocales().contains(locale)) {
  70. throw new LocaleNotLoadedException(locale);
  71. }
  72. this.locale = locale;
  73. }
  74. public String getLocale() {
  75. return locale;
  76. }
  77. public String getMonth(int month) {
  78. try {
  79. return LocaleService.getMonthNames(locale)[month];
  80. } catch (final LocaleNotLoadedException e) {
  81. getLogger().log(Level.SEVERE, "Error in getMonth", e);
  82. return null;
  83. }
  84. }
  85. public String getShortMonth(int month) {
  86. try {
  87. return LocaleService.getShortMonthNames(locale)[month];
  88. } catch (final LocaleNotLoadedException e) {
  89. getLogger().log(Level.SEVERE, "Error in getShortMonth", e);
  90. return null;
  91. }
  92. }
  93. public String getDay(int day) {
  94. try {
  95. return LocaleService.getDayNames(locale)[day];
  96. } catch (final LocaleNotLoadedException e) {
  97. getLogger().log(Level.SEVERE, "Error in getDay", e);
  98. return null;
  99. }
  100. }
  101. /**
  102. * Returns the localized short name of the specified day.
  103. *
  104. * @param day
  105. * the day, {@code 0} is {@code SUNDAY}
  106. * @return the localized short name
  107. */
  108. public String getShortDay(int day) {
  109. try {
  110. return LocaleService.getShortDayNames(locale)[day];
  111. } catch (final LocaleNotLoadedException e) {
  112. getLogger().log(Level.SEVERE, "Error in getShortDay", e);
  113. return null;
  114. }
  115. }
  116. /**
  117. * Returns the first day of the week, according to the used Locale.
  118. *
  119. * @return the localized first day of the week, {@code 0} is {@code SUNDAY}
  120. */
  121. public int getFirstDayOfWeek() {
  122. try {
  123. return LocaleService.getFirstDayOfWeek(locale);
  124. } catch (final LocaleNotLoadedException e) {
  125. getLogger().log(Level.SEVERE, "Error in getFirstDayOfWeek", e);
  126. return 0;
  127. }
  128. }
  129. /**
  130. * Returns whether the locale has twelve hour, or twenty four hour clock.
  131. *
  132. * @return {@code true} if the locale has twelve hour clock, {@code false}
  133. * for twenty four clock
  134. */
  135. public boolean isTwelveHourClock() {
  136. try {
  137. return LocaleService.isTwelveHourClock(locale);
  138. } catch (final LocaleNotLoadedException e) {
  139. getLogger().log(Level.SEVERE, "Error in isTwelveHourClock", e);
  140. return false;
  141. }
  142. }
  143. public String getClockDelimeter() {
  144. try {
  145. return LocaleService.getClockDelimiter(locale);
  146. } catch (final LocaleNotLoadedException e) {
  147. getLogger().log(Level.SEVERE, "Error in getClockDelimiter", e);
  148. return ":";
  149. }
  150. }
  151. private static final String[] DEFAULT_AMPM_STRINGS = { "AM", "PM" };
  152. public String[] getAmPmStrings() {
  153. try {
  154. return LocaleService.getAmPmStrings(locale);
  155. } catch (final LocaleNotLoadedException e) {
  156. // TODO can this practically even happen? Should die instead?
  157. getLogger().log(Level.SEVERE,
  158. "Locale not loaded, using fallback : AM/PM", e);
  159. return DEFAULT_AMPM_STRINGS;
  160. }
  161. }
  162. /**
  163. * Returns the first day of week of the specified {@code month}.
  164. *
  165. * @param month
  166. * the month, not {@code null}
  167. * @return the first day of week,
  168. */
  169. public int getStartWeekDay(Date month) {
  170. final Date dateForFirstOfThisMonth = new Date(month.getYear(),
  171. month.getMonth(), 1);
  172. int firstDay;
  173. try {
  174. firstDay = LocaleService.getFirstDayOfWeek(locale);
  175. } catch (final LocaleNotLoadedException e) {
  176. getLogger().log(Level.SEVERE, "Locale not loaded, using fallback 0",
  177. e);
  178. firstDay = 0;
  179. }
  180. int start = dateForFirstOfThisMonth.getDay() - firstDay;
  181. if (start < 0) {
  182. start += 7;
  183. }
  184. return start;
  185. }
  186. public static void setMilliseconds(Date date, int ms) {
  187. date.setTime(date.getTime() / 1000 * 1000 + ms);
  188. }
  189. public static int getMilliseconds(Date date) {
  190. if (date == null) {
  191. return 0;
  192. }
  193. return (int) (date.getTime() - date.getTime() / 1000 * 1000);
  194. }
  195. public static int getNumberOfDaysInMonth(Date date) {
  196. final int month = date.getMonth();
  197. if (month == 1 && isLeapYear(date)) {
  198. return 29;
  199. }
  200. return maxDaysInMonth[month];
  201. }
  202. public static boolean isLeapYear(Date date) {
  203. // Instantiate the date for 1st March of that year
  204. final Date firstMarch = new Date(date.getYear(), 2, 1);
  205. // Go back 1 day
  206. final long firstMarchTime = firstMarch.getTime();
  207. final long lastDayTimeFeb = firstMarchTime - (24 * 60 * 60 * 1000); // NUM_MILLISECS_A_DAY
  208. // Instantiate new Date with this time
  209. final Date febLastDay = new Date(lastDayTimeFeb);
  210. // Check for date in this new instance
  211. return (29 == febLastDay.getDate()) ? true : false;
  212. }
  213. public static boolean isSameDay(Date d1, Date d2) {
  214. return (getDayInt(d1) == getDayInt(d2));
  215. }
  216. public static boolean isInRange(Date date, Date rangeStart, Date rangeEnd,
  217. DateResolution resolution) {
  218. Date s;
  219. Date e;
  220. if (rangeStart.after(rangeEnd)) {
  221. s = rangeEnd;
  222. e = rangeStart;
  223. } else {
  224. e = rangeEnd;
  225. s = rangeStart;
  226. }
  227. long start = s.getYear() * 10000000000l;
  228. long end = e.getYear() * 10000000000l;
  229. long target = date.getYear() * 10000000000l;
  230. if (resolution == DateResolution.YEAR) {
  231. return (start <= target && end >= target);
  232. }
  233. start += s.getMonth() * 100000000l;
  234. end += e.getMonth() * 100000000l;
  235. target += date.getMonth() * 100000000l;
  236. if (resolution == DateResolution.MONTH) {
  237. return (start <= target && end >= target);
  238. }
  239. start += s.getDate() * 1000000l;
  240. end += e.getDate() * 1000000l;
  241. target += date.getDate() * 1000000l;
  242. if (resolution == DateResolution.DAY) {
  243. return (start <= target && end >= target);
  244. }
  245. start += s.getHours() * 10000l;
  246. end += e.getHours() * 10000l;
  247. target += date.getHours() * 10000l;
  248. start += s.getMinutes() * 100l;
  249. end += e.getMinutes() * 100l;
  250. target += date.getMinutes() * 100l;
  251. start += s.getSeconds();
  252. end += e.getSeconds();
  253. target += date.getSeconds();
  254. return (start <= target && end >= target);
  255. }
  256. private static int getDayInt(Date date) {
  257. final int y = date.getYear();
  258. final int m = date.getMonth();
  259. final int d = date.getDate();
  260. return ((y + 1900) * 10000 + m * 100 + d) * 1000000000;
  261. }
  262. /**
  263. * Returns the ISO-8601 week number of the given date.
  264. *
  265. * @param date
  266. * The date for which the week number should be resolved
  267. * @return The ISO-8601 week number for {@literal date}
  268. */
  269. public static int getISOWeekNumber(Date date) {
  270. int dayOfWeek = date.getDay(); // 0 == sunday
  271. // ISO 8601 use weeks that start on monday so we use
  272. // mon=1,tue=2,...sun=7;
  273. if (dayOfWeek == 0) {
  274. dayOfWeek = 7;
  275. }
  276. // Find nearest thursday (defines the week in ISO 8601). The week number
  277. // for the nearest thursday is the same as for the target date.
  278. int nearestThursdayDiff = 4 - dayOfWeek; // 4 is thursday
  279. Date nearestThursday = new Date(
  280. date.getTime() + nearestThursdayDiff * MILLISECONDS_PER_DAY);
  281. Date firstOfJanuary = new Date(nearestThursday.getYear(), 0, 1);
  282. long timeDiff = nearestThursday.getTime() - firstOfJanuary.getTime();
  283. // Rounding the result, as the division doesn't result in an integer
  284. // when the given date is inside daylight saving time period.
  285. int daysSinceFirstOfJanuary = (int) Math
  286. .round((double) timeDiff / MILLISECONDS_PER_DAY);
  287. int weekNumber = (daysSinceFirstOfJanuary) / 7 + 1;
  288. return weekNumber;
  289. }
  290. /**
  291. * Check if format contains the month name. If it does we manually convert
  292. * it to the month name since DateTimeFormat.format always uses the current
  293. * locale and will replace the month name wrong if current locale is
  294. * different from the locale set for the DateField.
  295. *
  296. * MMMM is converted into long month name, MMM is converted into short month
  297. * name. '' are added around the name to avoid that DateTimeFormat parses
  298. * the month name as a pattern.
  299. *
  300. * @param date
  301. * The date to convert
  302. * @param formatStr
  303. * The format string that might contain MMM or MMMM
  304. * @return
  305. */
  306. public String formatDate(Date date, String formatStr) {
  307. return formatDate(date, formatStr, null);
  308. }
  309. /**
  310. * Check if format contains the month name. If it does we manually convert
  311. * it to the month name since DateTimeFormat.format always uses the current
  312. * locale and will replace the month name wrong if current locale is
  313. * different from the locale set for the DateField.
  314. *
  315. * MMMM is converted into long month name, MMM is converted into short month
  316. * name. '' are added around the name to avoid that DateTimeFormat parses
  317. * the month name as a pattern.
  318. *
  319. * z is converted into the time zone name, using the specified
  320. * {@code timeZoneJSON}
  321. *
  322. * @param date
  323. * The date to convert
  324. * @param formatStr
  325. * The format string that might contain {@code MMM} or
  326. * {@code MMMM}
  327. * @param timeZone
  328. * The {@link TimeZone} used to replace {@code z}, can be
  329. * {@code null}
  330. *
  331. * @return the formatted date string
  332. * @since 8.2
  333. */
  334. public String formatDate(Date date, String formatStr, TimeZone timeZone) {
  335. /*
  336. * Format week numbers
  337. */
  338. formatStr = formatWeekNumbers(date, formatStr);
  339. /*
  340. * Format month and day names separately when locale for the
  341. * DateTimeService is not the same as the browser locale
  342. */
  343. formatStr = formatTimeZone(date, formatStr, timeZone);
  344. formatStr = formatMonthNames(date, formatStr);
  345. formatStr = formatDayNames(date, formatStr);
  346. // Format uses the browser locale
  347. DateTimeFormat format = DateTimeFormat.getFormat(formatStr);
  348. String result = format.format(date);
  349. return result;
  350. }
  351. /*
  352. * Calculate number of the week in the year based on Date
  353. * Note, support for "ww" is missing GWT DateTimeFormat
  354. * and java.util.Calendar is not supported in GWT
  355. * Hence DIY method needed
  356. */
  357. private String getWeek(Date date) {
  358. int year = date.getYear()+1900;
  359. int month = date.getMonth();
  360. int day = date.getDate()+1;
  361. int weekDay = date.getDay();
  362. if (weekDay == 6) {
  363. weekDay = 0;
  364. } else {
  365. weekDay = weekDay - 1;
  366. }
  367. boolean leap = false;
  368. if (((year % 4) == 0) && (((year % 100) != 0) || ((year % 400) == 0))) {
  369. leap = true;
  370. }
  371. int week;
  372. if (leap) {
  373. week = countWeek(leapYearDays, month, day, weekDay);
  374. } else {
  375. week = countWeek(yearDays, month, day, weekDay);
  376. }
  377. return ""+week;
  378. }
  379. private int countWeek(int[] days, int month, int day, int weekDay) {
  380. return ((days[month] + day) - (weekDay + 7) % 7 + 7) / 7;
  381. }
  382. private String formatWeekNumbers(Date date, String formatStr) {
  383. if (formatStr.contains("ww")) {
  384. String weekNumber = getWeek(date);
  385. formatStr = formatStr.replaceAll("ww", weekNumber);
  386. }
  387. return formatStr;
  388. }
  389. private String formatDayNames(Date date, String formatStr) {
  390. if (formatStr.contains("EEEE")) {
  391. String dayName = getDay(date.getDay());
  392. if (dayName != null) {
  393. /*
  394. * Replace 4 or more E:s with the quoted day name. Also
  395. * concatenate generated string with any other string prepending
  396. * or following the EEEE pattern, i.e. 'EEEE'ta ' becomes 'DAYta
  397. * ' and not 'DAY''ta ', 'ab'EEEE becomes 'abDAY', 'x'EEEE'y'
  398. * becomes 'xDAYy'.
  399. */
  400. formatStr = formatStr.replaceAll("'([E]{4,})'", dayName);
  401. formatStr = formatStr.replaceAll("([E]{4,})'", "'" + dayName);
  402. formatStr = formatStr.replaceAll("'([E]{4,})", dayName + "'");
  403. formatStr = formatStr.replaceAll("[E]{4,}",
  404. "'" + dayName + "'");
  405. }
  406. }
  407. if (formatStr.contains("EEE")) {
  408. String dayName = getShortDay(date.getDay());
  409. if (dayName != null) {
  410. /*
  411. * Replace 3 or more E:s with the quoted month name. Also
  412. * concatenate generated string with any other string prepending
  413. * or following the EEE pattern, i.e. 'EEE'ta ' becomes 'DAYta '
  414. * and not 'DAY''ta ', 'ab'EEE becomes 'abDAY', 'x'EEE'y'
  415. * becomes 'xDAYy'.
  416. */
  417. formatStr = formatStr.replaceAll("'([E]{3,})'", dayName);
  418. formatStr = formatStr.replaceAll("([E]{3,})'", "'" + dayName);
  419. formatStr = formatStr.replaceAll("'([E]{3,})", dayName + "'");
  420. formatStr = formatStr.replaceAll("[E]{3,}",
  421. "'" + dayName + "'");
  422. }
  423. }
  424. return formatStr;
  425. }
  426. private String formatMonthNames(Date date, String formatStr) {
  427. if (formatStr.contains("MMMM")) {
  428. String monthName = getMonth(date.getMonth());
  429. if (monthName != null) {
  430. /*
  431. * Replace 4 or more M:s with the quoted month name. Also
  432. * concatenate generated string with any other string prepending
  433. * or following the MMMM pattern, i.e. 'MMMM'ta ' becomes
  434. * 'MONTHta ' and not 'MONTH''ta ', 'ab'MMMM becomes 'abMONTH',
  435. * 'x'MMMM'y' becomes 'xMONTHy'.
  436. */
  437. formatStr = formatStr.replaceAll("'([M]{4,})'", monthName);
  438. formatStr = formatStr.replaceAll("([M]{4,})'", "'" + monthName);
  439. formatStr = formatStr.replaceAll("'([M]{4,})", monthName + "'");
  440. formatStr = formatStr.replaceAll("[M]{4,}",
  441. "'" + monthName + "'");
  442. }
  443. }
  444. if (formatStr.contains("MMM")) {
  445. String monthName = getShortMonth(date.getMonth());
  446. if (monthName != null) {
  447. /*
  448. * Replace 3 or more M:s with the quoted month name. Also
  449. * concatenate generated string with any other string prepending
  450. * or following the MMM pattern, i.e. 'MMM'ta ' becomes 'MONTHta
  451. * ' and not 'MONTH''ta ', 'ab'MMM becomes 'abMONTH', 'x'MMM'y'
  452. * becomes 'xMONTHy'.
  453. */
  454. formatStr = formatStr.replaceAll("'([M]{3,})'", monthName);
  455. formatStr = formatStr.replaceAll("([M]{3,})'", "'" + monthName);
  456. formatStr = formatStr.replaceAll("'([M]{3,})", monthName + "'");
  457. formatStr = formatStr.replaceAll("[M]{3,}",
  458. "'" + monthName + "'");
  459. }
  460. }
  461. return formatStr;
  462. }
  463. private String formatTimeZone(Date date, String formatStr,
  464. TimeZone timeZone) {
  465. // if 'z' is found outside quotes and timeZone is used
  466. if (getIndexOf(formatStr, 'z') != -1 && timeZone != null) {
  467. return replaceTimeZone(formatStr, timeZone.getShortName(date));
  468. }
  469. return formatStr;
  470. }
  471. /**
  472. * Replaces the {@code z} characters of the specified {@code formatStr} with
  473. * the given {@code timeZoneName}.
  474. *
  475. * @param formatStr
  476. * The format string, which is the pattern describing the date
  477. * and time format
  478. * @param timeZoneName
  479. * the time zone name
  480. * @return the format string, with {@code z} replaced (if found)
  481. */
  482. private static String replaceTimeZone(String formatStr,
  483. String timeZoneName) {
  484. // search for 'z' outside the quotes (inside quotes is escaped)
  485. int start = getIndexOf(formatStr, 'z');
  486. if (start == -1) {
  487. return formatStr;
  488. }
  489. // if there are multiple consecutive 'z', treat them as one
  490. int end = start;
  491. while (end + 1 < formatStr.length()
  492. && formatStr.charAt(end + 1) == 'z') {
  493. end++;
  494. }
  495. return formatStr.substring(0, start) + "'" + timeZoneName + "'"
  496. + formatStr.substring(end + 1);
  497. }
  498. /**
  499. * Returns the first index of the specified {@code ch}, which is outside the
  500. * quotes.
  501. */
  502. private static int getIndexOf(String str, char ch) {
  503. boolean inQuote = false;
  504. for (int i = 0; i < str.length(); i++) {
  505. char c = str.charAt(i);
  506. if (c == '\'') {
  507. if (i + 1 < str.length() && str.charAt(i + 1) == '\'') {
  508. i++;
  509. } else {
  510. inQuote ^= true;
  511. }
  512. } else if (c == ch && !inQuote) {
  513. return i;
  514. }
  515. }
  516. return -1;
  517. }
  518. /**
  519. * Replaces month names in the entered date with the name in the current
  520. * browser locale.
  521. *
  522. * @param enteredDate
  523. * Date string e.g. "5 May 2010"
  524. * @param formatString
  525. * Format string e.g. "d M yyyy"
  526. * @return The date string where the month names have been replaced by the
  527. * browser locale version
  528. */
  529. private String parseMonthName(String enteredDate, String formatString) {
  530. LocaleInfo browserLocale = LocaleInfo.getCurrentLocale();
  531. if (browserLocale.getLocaleName().equals(getLocale())) {
  532. // No conversion needs to be done when locales match
  533. return enteredDate;
  534. }
  535. String[] browserMonthNames = browserLocale.getDateTimeConstants()
  536. .months();
  537. String[] browserShortMonthNames = browserLocale.getDateTimeConstants()
  538. .shortMonths();
  539. if (formatString.contains("MMMM")) {
  540. // Full month name
  541. for (int i = 0; i < 12; i++) {
  542. enteredDate = enteredDate.replaceAll(getMonth(i),
  543. browserMonthNames[i]);
  544. }
  545. }
  546. if (formatString.contains("MMM")) {
  547. // Short month name
  548. for (int i = 0; i < 12; i++) {
  549. enteredDate = enteredDate.replaceAll(getShortMonth(i),
  550. browserShortMonthNames[i]);
  551. }
  552. }
  553. return enteredDate;
  554. }
  555. /**
  556. * Parses the given date string using the given format string and the locale
  557. * set in this DateTimeService instance.
  558. *
  559. * @param dateString
  560. * Date string e.g. "1 February 2010"
  561. * @param formatString
  562. * Format string e.g. "d MMMM yyyy"
  563. * @param lenient
  564. * true to use lenient parsing, false to use strict parsing
  565. * @return A Date object representing the dateString. Never returns null.
  566. * @throws IllegalArgumentException
  567. * if the parsing fails
  568. *
  569. */
  570. public Date parseDate(String dateString, String formatString,
  571. boolean lenient) throws IllegalArgumentException {
  572. /* DateTimeFormat uses the browser's locale */
  573. DateTimeFormat format = DateTimeFormat.getFormat(formatString);
  574. /*
  575. * Parse month names separately when locale for the DateTimeService is
  576. * not the same as the browser locale
  577. */
  578. dateString = parseMonthName(dateString, formatString);
  579. Date date;
  580. if (lenient) {
  581. date = format.parse(dateString);
  582. } else {
  583. date = format.parseStrict(dateString);
  584. }
  585. // Some version of Firefox sets the timestamp to 0 if parsing fails.
  586. if (date != null && date.getTime() == 0) {
  587. throw new IllegalArgumentException(
  588. "Parsing of '" + dateString + "' failed");
  589. }
  590. return date;
  591. }
  592. private static Logger getLogger() {
  593. return Logger.getLogger(DateTimeService.class.getName());
  594. }
  595. }