All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.dspace.content.DCDate Maven / Gradle / Ivy

The newest version!
/**
 * The contents of this file are subject to the license and copyright
 * detailed in the LICENSE and NOTICE files at the root of the source
 * tree and available online at
 *
 * http://www.dspace.org/license/
 */
package org.dspace.content;

import java.text.DateFormatSymbols;
import java.time.LocalDate;
import java.time.Year;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import org.apache.logging.log4j.Logger;

// FIXME: Not very robust - assumes dates will always be valid

/**
 * Dublin Core date utility class
 * 

* Dates in the DSpace database are held in the ISO 8601 format. They are always * stored in UTC, converting to and from the current time zone. In practice only dates * with a time component need to be converted. *

* YYYY-MM-DDThh:mm:ss *

* There are four levels of granularity, depending on how much date information * is available: year, month, day, time. *

* Examples: {@code 1994-05-03T15:30:24}, {@code 1995-10-04}, * {@code 2001-10}, {@code 1975} * * @author Robert Tansley * @author Larry Stone */ public class DCDate { /** * Logger */ private static Logger log = org.apache.logging.log4j.LogManager.getLogger(DCDate.class); // components of time in UTC private ZonedDateTime calendar = null; // components of time in local zone private ZonedDateTime localCalendar = null; private enum DateGran { YEAR, MONTH, DAY, TIME } DateGran granularity = null; // Full ISO 8601 is e.g. "2009-07-16T13:59:21Z" private final DateTimeFormatter fullIso = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'") .withZone(ZoneOffset.UTC); // without Z private final DateTimeFormatter fullIso2 = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss") .withZone(ZoneOffset.UTC); // without seconds private final DateTimeFormatter fullIso3 = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm") .withZone(ZoneOffset.UTC); // without minutes private final DateTimeFormatter fullIso4 = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH") .withZone(ZoneOffset.UTC); // Date-only ISO 8601 is e.g. "2009-07-16" private final DateTimeFormatter dateIso = DateTimeFormatter.ofPattern("yyyy-MM-dd") .withZone(ZoneOffset.UTC); // Year-Month-only ISO 8601 is e.g. "2009-07" private final DateTimeFormatter yearMonthIso = DateTimeFormatter.ofPattern("yyyy-MM") .withZone(ZoneOffset.UTC); // just year, "2009" private final DateTimeFormatter yearIso = DateTimeFormatter.ofPattern("yyyy") .withZone(ZoneOffset.UTC); // Additional iso-like format which contains milliseconds private final DateTimeFormatter fullIsoWithMs = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'.000'") .withZone(ZoneOffset.UTC); private static Map dfsLocaleMap = new HashMap<>(); /** * Construct a date object from a Java Instant object. * * @param date the Java Instant object. */ public DCDate(ZonedDateTime date) { if (date == null) { return; } // By definition a Date has a time component so always set the granularity to TIME. granularity = DateGran.TIME; // Set the local calendar based on timezone of the passed in ZonedDateTime localCalendar = date; // Time is assumed to be in UTC timezone because DSpace stores all dates internally as UTC calendar = date.withZoneSameInstant(ZoneOffset.UTC); } /** * Construct a date object from a bunch of component parts. The date passed in is assumed to be in the current * time zone. Unknown values should be given as -1. * * @param yyyy the year * @param mm the month * @param dd the day * @param hh the hours * @param mn the minutes * @param ss the seconds */ public DCDate(int yyyy, int mm, int dd, int hh, int mn, int ss) { // default values int lyear = 0; int lhours = 0; int lminutes = 0; int lseconds = 0; int lmonth = 1; int lday = 1; if (yyyy > 0) { lyear = yyyy; granularity = DateGran.YEAR; } if (mm > 0) { lmonth = mm; granularity = DateGran.MONTH; } if (dd > 0) { lday = dd; granularity = DateGran.DAY; } if (hh >= 0) { lhours = hh; granularity = DateGran.TIME; } if (mn >= 0) { lminutes = mn; granularity = DateGran.TIME; } if (ss >= 0) { lseconds = ss; granularity = DateGran.TIME; } // Set the local calendar based on system default timezone localCalendar = ZonedDateTime.of(lyear, lmonth, lday, lhours, lminutes, lseconds, 0, ZoneId.systemDefault()); if (granularity == DateGran.TIME) { // Now set the UTC equivalent. calendar = localCalendar.withZoneSameInstant(ZoneOffset.UTC); } else { // No Time component so just set the UTC date to be the same as the local Year, Month, and Day. calendar = ZonedDateTime.of(localCalendar.getYear(), localCalendar.getMonthValue(), localCalendar.getDayOfMonth(), 0, 0, 0, 0, ZoneOffset.UTC); } } /** * Construct a date from a Dublin Core value * * @param fromDC the date string, in ISO 8601 (no timezone, always use UTC) */ public DCDate(String fromDC) { // An empty date is OK if ((fromDC == null) || fromDC.equals("")) { return; } // default granularity granularity = DateGran.TIME; // Try to parse a full date/time using various formats ZonedDateTime date = tryParse(fullIso, fromDC); if (date == null) { date = tryParse(fullIso2, fromDC); } if (date == null) { date = tryParse(fullIso3, fromDC); } if (date == null) { date = tryParse(fullIso4, fromDC); } if (date == null) { date = tryParse(fullIsoWithMs, fromDC); } // Seems there is no time component to the date, so we'll need to use specialized java.time classes // to parse out the day, month or year. // Try to parse as just a date (no time) in UTC. if (date == null) { try { date = LocalDate.parse(fromDC, dateIso).atStartOfDay(ZoneId.systemDefault()); } catch (DateTimeParseException e) { date = null; } if (date != null) { granularity = DateGran.DAY; } } // Try to parse as just a month & year in UTC if (date == null) { try { date = YearMonth.parse(fromDC, yearMonthIso).atDay(1).atStartOfDay(ZoneId.systemDefault()); } catch (DateTimeParseException e) { date = null; } if (date != null) { granularity = DateGran.MONTH; } } // Try to parse as just a year in UTC if (date == null) { try { date = Year.parse(fromDC, yearIso).atMonth(1).atDay(1).atStartOfDay(ZoneId.systemDefault()); } catch (DateTimeParseException e) { date = null; } if (date != null) { granularity = DateGran.YEAR; } } if (date == null) { log.warn("Mangled date: " + fromDC + " ..failed all attempts to parse as date."); } else { // By default, we parse strings into UTC time. So the "date" object is already in UTC timezone calendar = date; // Now set the local equivalent based on system default timezone if (granularity == DateGran.TIME) { localCalendar = date.withZoneSameInstant(ZoneId.systemDefault()); } else { // No Time component so just set the local date to be the same as the UTC Year, Month, and Day. localCalendar = ZonedDateTime.of(calendar.getYear(), calendar.getMonth().getValue(), calendar.getDayOfMonth(), 0, 0, 0, 0, ZoneOffset.UTC); } } } // Attempt to parse, swallowing errors; return null for failure. private synchronized ZonedDateTime tryParse(DateTimeFormatter formatter, String source) { try { return ZonedDateTime.parse(source, formatter); } catch (DateTimeParseException e) { return null; } } /** * Get the year, adjusting for current time zone. * * @return the year */ public int getYear() { return !withinGranularity(DateGran.YEAR) ? -1 : localCalendar.getYear(); } /** * Get the month, adjusting for current time zone. * * @return the month */ public int getMonth() { return !withinGranularity(DateGran.MONTH) ? -1 : localCalendar.getMonthValue(); } /** * Get the day, adjusting for current time zone. * * @return the day */ public int getDay() { return !withinGranularity(DateGran.DAY) ? -1 : localCalendar.getDayOfMonth(); } /** * Get the hour, adjusting for current time zone. * * @return the hour */ public int getHour() { return !withinGranularity(DateGran.TIME) ? -1 : localCalendar.getHour(); } /** * Get the minute, adjusting for current time zone. * * @return the minute */ public int getMinute() { return !withinGranularity(DateGran.TIME) ? -1 : localCalendar.getMinute(); } /** * Get the second, adjusting for current time zone. * * @return the second */ public int getSecond() { return !withinGranularity(DateGran.TIME) ? -1 : localCalendar.getSecond(); } /** * Get the year in UTC. * * @return the year */ public int getYearUTC() { return !withinGranularity(DateGran.YEAR) ? -1 : calendar.getYear(); } /** * Get the month in UTC. * * @return the month */ public int getMonthUTC() { return !withinGranularity(DateGran.MONTH) ? -1 : calendar.getMonthValue(); } /** * Get the day in UTC. * * @return the day */ public int getDayUTC() { return !withinGranularity(DateGran.DAY) ? -1 : calendar.getDayOfMonth(); } /** * Get the hour in UTC. * * @return the hour */ public int getHourUTC() { return !withinGranularity(DateGran.TIME) ? -1 : calendar.getHour(); } /** * Get the minute in UTC. * * @return the minute */ public int getMinuteUTC() { return !withinGranularity(DateGran.TIME) ? -1 : calendar.getMinute(); } /** * Get the second in UTC. * * @return the second */ public int getSecondUTC() { return !withinGranularity(DateGran.TIME) ? -1 : calendar.getSecond(); } /** * Get the date as a string to put back in the Dublin Core. Use the UTC/GMT calendar version. * * @return The date as a string. */ @Override public String toString() { if (calendar == null) { return "null"; } return toStringInternal(); } private synchronized String toStringInternal() { if (granularity == DateGran.YEAR) { return String.format("%4d", getYearUTC()); } else if (granularity == DateGran.MONTH) { return String.format("%4d-%02d", getYearUTC(), getMonthUTC()); } else if (granularity == DateGran.DAY) { return String.format("%4d-%02d-%02d", getYearUTC(), getMonthUTC(), getDayUTC()); } else { return fullIso.format(calendar); } } /** * Get the date as a Java ZonedDateTime object in UTC timezone. * * @return a ZonedDateTime object */ public ZonedDateTime toDate() { if (calendar == null) { return null; } else { return calendar; } } /** * Format a human-readable version of the DCDate, with optional time. * This needs to be in DCDate because it depends on the granularity of * the original time. * * FIXME: This should probably be replaced with a localized DateFormat. * * @param showTime if true, display the time with the date * @param isLocalTime if true, adjust for local time zone, otherwise UTC * @param locale locale of the user * @return String with the date in a human-readable form. */ public String displayDate(boolean showTime, boolean isLocalTime, Locale locale) { if (isLocalTime) { return displayLocalDate(showTime, locale); } else { return displayUTCDate(showTime, locale); } } public String displayLocalDate(boolean showTime, Locale locale) { // forcibly truncate month name to 3 chars -- XXX FIXME? String monthName = getMonthName(getMonth(), locale); if (monthName.length() > 2) { monthName = monthName.substring(0, 3); } // display date and time if (showTime && granularity == DateGran.TIME) { return String.format("%d-%s-%4d %02d:%02d:%02d", getDay(), monthName, getYear(), getHour(), getMinute(), getSecond()); } else if (granularity == DateGran.YEAR) { return String.format("%4d", getYear()); } else if (granularity == DateGran.MONTH) { return String.format("%s-%4d", monthName, getYear()); } else { return String.format("%d-%s-%4d", getDay(), monthName, getYear()); } } public String displayUTCDate(boolean showTime, Locale locale) { // forcibly truncate month name to 3 chars -- XXX FIXME? String monthName = getMonthName(getMonthUTC(), locale); if (monthName.length() > 2) { monthName = monthName.substring(0, 3); } // display date and time if (showTime && granularity == DateGran.TIME) { return String .format("%d-%s-%4d %02d:%02d:%02d", getDayUTC(), monthName, getYearUTC(), getHourUTC(), getMinuteUTC(), getSecondUTC()); } else if (granularity == DateGran.YEAR) { return String.format("%4d", getYearUTC()); } else if (granularity == DateGran.MONTH) { return String.format("%s-%4d", monthName, getYearUTC()); } else { return String.format("%d-%s-%4d", getDayUTC(), monthName, getYearUTC()); } } /** * Test if the requested level of granularity is within that of the date. * * @param dg The requested level of granularity. * @return true or false. */ private boolean withinGranularity(DateGran dg) { if (granularity == DateGran.TIME) { if ((dg == DateGran.TIME) || (dg == DateGran.DAY) || (dg == DateGran.MONTH) || (dg == DateGran.YEAR)) { return true; } } if (granularity == DateGran.DAY) { if ((dg == DateGran.DAY) || (dg == DateGran.MONTH) || (dg == DateGran.YEAR)) { return true; } } if (granularity == DateGran.MONTH) { if ((dg == DateGran.MONTH) || (dg == DateGran.YEAR)) { return true; } } if (granularity == DateGran.YEAR) { if (dg == DateGran.YEAR) { return true; } } return false; } /************** Some utility methods ******************/ /** * Get a date representing the current instant in UTC time. * * @return a DSpaceDate object representing the current instant. */ public static DCDate getCurrent() { return new DCDate(ZonedDateTime.now(ZoneOffset.UTC)); } /** * Get a month's name for a month between 1 and 12. Any invalid month value * (e.g. 0 or -1) will return a value of "Unspecified". * * @param m the month number * @param locale which locale to render the month name in * @return the month name. */ public static String getMonthName(int m, Locale locale) { if ((m > 0) && (m < 13)) { DateFormatSymbols dfs = dfsLocaleMap.get(locale); if (dfs == null) { dfs = new DateFormatSymbols(locale); dfsLocaleMap.put(locale, dfs); } return dfs.getMonths()[m - 1]; } else { return "Unspecified"; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy