From 477d8c6d720d06875f7017aa429d2bcd4a76238c Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Wed, 16 Dec 2020 17:38:47 -0500 Subject: [PATCH] Implemented the DoD, tested and commented the code --- .../discovery/search/Bundle.properties-MERGED | 6 +- .../discovery/search/DiscoveryKeyUtils.java | 142 +++++++----------- .../discovery/search/DomainSearch.java | 6 +- .../search/DomainSearchCacheLoader.java | 9 +- .../discovery/search/ResultDomain.java | 2 +- .../autopsy/discovery/search/SearchData.java | 8 +- .../discovery/search/SearchFiltering.java | 9 +- .../discovery/ui/Bundle.properties-MERGED | 12 ++ .../discovery/ui/DomainSummaryPanel.java | 29 +++- .../discovery/ui/MonthAbbreviation.java | 80 ++++++++++ 10 files changed, 195 insertions(+), 108 deletions(-) create mode 100755 Core/src/org/sleuthkit/autopsy/discovery/ui/MonthAbbreviation.java diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/discovery/search/Bundle.properties-MERGED index 6a029f0759..cd823d353b 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/search/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/discovery/search/Bundle.properties-MERGED @@ -1,3 +1,7 @@ +# {0} - month abbreviation +# {1} - day of month +# {2} - year +DiscoveryAttributes.ActivityDateGroupKey.getDisplayNameTemplate=Week of {0} {1}, {2} DiscoveryAttributes.GroupingAttributeType.datasource.displayName=Data Source DiscoveryAttributes.GroupingAttributeType.fileType.displayName=File Type DiscoveryAttributes.GroupingAttributeType.firstDate.displayName=First Activity Date @@ -20,11 +24,9 @@ DiscoveryKeyUtils.DataSourceGroupKey.datasourceAndID={0}(ID: {1}) # {0} - Data source ID DiscoveryKeyUtils.DataSourceGroupKey.idOnly=Data source (ID: {0}) DiscoveryKeyUtils.FileTagGroupKey.noSets=None -DiscoveryKeyUtils.FirstActivityDateGroupKey.noDate=No Date Available DiscoveryKeyUtils.HashHitsGroupKey.noHashHits=None DiscoveryKeyUtils.InterestingItemGroupKey.noSets=None DiscoveryKeyUtils.KeywordListGroupKey.noKeywords=None -DiscoveryKeyUtils.LastActivityDateGroupKey.noDate=No Date Available DiscoveryKeyUtils.NoGroupingGroupKey.allFiles=All Files DiscoveryKeyUtils.ObjectDetectedGroupKey.noSets=None # {0} - domain diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryKeyUtils.java b/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryKeyUtils.java index d55242c191..a3c8b4a584 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryKeyUtils.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryKeyUtils.java @@ -18,18 +18,21 @@ */ package org.sleuthkit.autopsy.discovery.search; -import java.text.SimpleDateFormat; +import java.time.DayOfWeek; +import java.time.Instant; +import java.time.ZonedDateTime; +import java.time.temporal.TemporalAdjusters; import java.util.Collections; -import java.util.Date; import java.util.List; -import java.util.Locale; import java.util.Objects; -import java.util.concurrent.TimeUnit; +import java.util.TimeZone; import java.util.logging.Level; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.autopsy.discovery.search.SearchData.PageViews; +import org.sleuthkit.autopsy.discovery.ui.MonthAbbreviation; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.SleuthkitCase; @@ -1134,29 +1137,34 @@ public class DiscoveryKeyUtils { */ static class LastActivityDateGroupKey extends GroupKey { - private final Long epochDate; - private final String dateNameString; + private ZonedDateTime currentWeekCutOff; /** * Construct a new LastActivityDateGroupKey. * * @param result The Result to create the group key for. */ - @NbBundle.Messages({ - "DiscoveryKeyUtils.LastActivityDateGroupKey.noDate=No Date Available"}) LastActivityDateGroupKey(Result result) { if (result instanceof ResultDomain) { - epochDate = ((ResultDomain) result).getActivityEnd(); - dateNameString = new SimpleDateFormat("yyyy/MM/dd", Locale.getDefault()).format(new Date(TimeUnit.SECONDS.toMillis(epochDate))); + ResultDomain domainResult = ((ResultDomain) result); + currentWeekCutOff = getCurrentWeekCutOff(domainResult.getActivityEnd(), domainResult); } else { - epochDate = Long.MAX_VALUE; - dateNameString = Bundle.DiscoveryKeyUtils_LastActivityDateGroupKey_noDate(); + throw new IllegalArgumentException("Expected a domain result only."); } } + @NbBundle.Messages({ + "# {0} - month abbreviation", + "# {1} - day of month", + "# {2} - year", + "DiscoveryAttributes.ActivityDateGroupKey.getDisplayNameTemplate=Week of {0} {1}, {2}" + }) @Override String getDisplayName() { - return getDateNameString(); + MonthAbbreviation currentCutOffMonth = MonthAbbreviation.fromMonthValue(currentWeekCutOff.getMonthValue()); + return Bundle.DiscoveryAttributes_ActivityDateGroupKey_getDisplayNameTemplate( + currentCutOffMonth.toString(), Integer.toString(currentWeekCutOff.getDayOfMonth()), + Integer.toString(currentWeekCutOff.getYear())); } @Override @@ -1170,53 +1178,40 @@ public class DiscoveryKeyUtils { } LastActivityDateGroupKey dateGroupKey = (LastActivityDateGroupKey) otherKey; - return getDateNameString().equals(dateGroupKey.getDateNameString()); + return getDisplayName().equals(dateGroupKey.getDisplayName()); } @Override public int hashCode() { - return Objects.hash(getDateNameString()); + return Objects.hash(getDisplayName()); } @Override public int compareTo(GroupKey otherGroupKey) { if (otherGroupKey instanceof LastActivityDateGroupKey) { LastActivityDateGroupKey otherDateGroupKey = (LastActivityDateGroupKey) otherGroupKey; - - // Put the empty list at the end - if (this.getEpochDate().equals(Long.MAX_VALUE)) { - if (otherDateGroupKey.getEpochDate().equals(Long.MAX_VALUE)) { - return 0; - } else { - return 1; - } - } else if (otherDateGroupKey.getEpochDate().equals(Long.MAX_VALUE)) { - return -1; - } - - return getDateNameString().compareTo(otherDateGroupKey.getDateNameString()); + return Long.compare(otherDateGroupKey.currentWeekCutOff.toEpochSecond(), currentWeekCutOff.toEpochSecond()); } else { return compareClassNames(otherGroupKey); } } - - /** - * Get the date this group is for as a Long. - * - * @return The date. - */ - Long getEpochDate() { - return epochDate; - } - - /** - * Get the name which identifies this group. - * - * @return The dateNameString. - */ - String getDateNameString() { - return dateNameString; - } + } + + /** + * Get the next closed Sunday given an epoch time and timezone. + * Dates for grouping are managed on a weekly basis. Each Sunday + * acts as the boundary and representative for the week. + */ + private static ZonedDateTime getCurrentWeekCutOff(long epochSeconds, ResultDomain domainResult) { + Instant startActivityAsInsant = Instant.ofEpochSecond(epochSeconds); + // Determines the timezone using the settings panel or value parsed from the + // parent data source + TimeZone currentTimeZone = ContentUtils.getTimeZone(domainResult.getDataSource()); + // Convert to a datetime using epoch and timezone. + ZonedDateTime startActivityAsDateTime = ZonedDateTime.ofInstant(startActivityAsInsant, currentTimeZone.toZoneId()); + // Get the closest Sunday, which is the cut off for the current week. + // Use this cut off to perform grouping and comparing. + return startActivityAsDateTime.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY)); } /** @@ -1224,29 +1219,28 @@ public class DiscoveryKeyUtils { */ static class FirstActivityDateGroupKey extends GroupKey { - private final Long epochDate; - private final String dateNameString; + private ZonedDateTime currentWeekCutOff; /** * Construct a new FirstActivityDateGroupKey. * * @param result The Result to create the group key for. */ - @NbBundle.Messages({ - "DiscoveryKeyUtils.FirstActivityDateGroupKey.noDate=No Date Available"}) FirstActivityDateGroupKey(Result result) { if (result instanceof ResultDomain) { - epochDate = ((ResultDomain) result).getActivityStart(); - dateNameString = new SimpleDateFormat("yyyy/MM/dd", Locale.getDefault()).format(new Date(TimeUnit.SECONDS.toMillis(epochDate))); + ResultDomain domainResult = ((ResultDomain) result); + currentWeekCutOff = getCurrentWeekCutOff(domainResult.getActivityStart(), domainResult); } else { - epochDate = Long.MAX_VALUE; - dateNameString = Bundle.DiscoveryKeyUtils_FirstActivityDateGroupKey_noDate(); + throw new IllegalArgumentException("Expected a domain result only."); } } - + @Override String getDisplayName() { - return getDateNameString(); + MonthAbbreviation currentCutOffMonth = MonthAbbreviation.fromMonthValue(currentWeekCutOff.getMonthValue()); + return Bundle.DiscoveryAttributes_ActivityDateGroupKey_getDisplayNameTemplate( + currentCutOffMonth.toString(), Integer.toString(currentWeekCutOff.getDayOfMonth()), + Integer.toString(currentWeekCutOff.getYear())); } @Override @@ -1260,53 +1254,23 @@ public class DiscoveryKeyUtils { } FirstActivityDateGroupKey dateGroupKey = (FirstActivityDateGroupKey) otherKey; - return getDateNameString().equals(dateGroupKey.getDateNameString()); + return getDisplayName().equals(dateGroupKey.getDisplayName()); } @Override public int hashCode() { - return Objects.hash(getDateNameString()); + return Objects.hash(getDisplayName()); } @Override public int compareTo(GroupKey otherGroupKey) { if (otherGroupKey instanceof FirstActivityDateGroupKey) { FirstActivityDateGroupKey otherDateGroupKey = (FirstActivityDateGroupKey) otherGroupKey; - - // Put the empty list at the end - if (this.getEpochDate().equals(Long.MAX_VALUE)) { - if (otherDateGroupKey.getEpochDate().equals(Long.MAX_VALUE)) { - return 0; - } else { - return 1; - } - } else if (otherDateGroupKey.getEpochDate().equals(Long.MAX_VALUE)) { - return -1; - } - - return getDateNameString().compareTo(otherDateGroupKey.getDateNameString()); + return Long.compare(otherDateGroupKey.currentWeekCutOff.toEpochSecond(), currentWeekCutOff.toEpochSecond()); } else { return compareClassNames(otherGroupKey); } } - - /** - * Get the date this group is for as a Long. - * - * @return The date. - */ - Long getEpochDate() { - return epochDate; - } - - /** - * Get the name which identifies this group. - * - * @return The dateNameString. - */ - String getDateNameString() { - return dateNameString; - } } /** diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearch.java b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearch.java index 290530bf54..0ceda46203 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearch.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearch.java @@ -24,12 +24,15 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.TimeZone; import org.apache.commons.lang3.StringUtils; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.autopsy.discovery.search.DiscoveryKeyUtils.GroupKey; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TimeUtilities; import org.sleuthkit.datamodel.TskCoreException; /** @@ -245,7 +248,8 @@ public class DomainSearch { private String getDate(BlackboardArtifact artifact) throws TskCoreException { for (BlackboardAttribute attribute : artifact.getAttributes()) { if (attribute.getAttributeType().getTypeName().startsWith("TSK_DATETIME")) { - String dateString = attribute.getDisplayString(); + TimeZone timeZone = ContentUtils.getTimeZone(artifact); + String dateString = TimeUtilities.epochToTime(attribute.getValueLong(), timeZone); if (dateString.length() >= 10) { return dateString.substring(0, 10); } diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchCacheLoader.java b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchCacheLoader.java index bbded2952e..9aaa375eba 100755 --- a/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchCacheLoader.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchCacheLoader.java @@ -212,13 +212,14 @@ class DomainSearchCacheLoader extends CacheLoader 0"); return Pair.of( - whereClause.toString() + ((artifactTypeFilter != null) ? " AND (" + artifactTypeFilter + ")" : ""), + whereClause.toString() + " AND (" + artifactTypeFilter.getWhereClause(Arrays.asList(TSK_WEB_ACCOUNT_TYPE)) + ")", havingClause.toString() ); } diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/ResultDomain.java b/Core/src/org/sleuthkit/autopsy/discovery/search/ResultDomain.java index ad8776e847..5da9567887 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/search/ResultDomain.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/search/ResultDomain.java @@ -151,7 +151,7 @@ public class ResultDomain extends Result { } @Override - public Content getDataSource() throws TskCoreException { + public Content getDataSource() { return this.dataSource; } diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/SearchData.java b/Core/src/org/sleuthkit/autopsy/discovery/search/SearchData.java index b59556015a..d94a189f11 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/search/SearchData.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/search/SearchData.java @@ -36,7 +36,13 @@ import org.sleuthkit.datamodel.BlackboardArtifact; public final class SearchData { private final static long BYTES_PER_MB = 1000000; - private static final Set DOMAIN_ARTIFACT_TYPES = EnumSet.of(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK, BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_CACHE, BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_COOKIE, BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY, BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY); + private static final Set DOMAIN_ARTIFACT_TYPES = + EnumSet.of(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK, + BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_CACHE, + BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_COOKIE, + BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, + BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY, + BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY); /** diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/SearchFiltering.java b/Core/src/org/sleuthkit/autopsy/discovery/search/SearchFiltering.java index 8fab7e84eb..d5142c08c2 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/search/SearchFiltering.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/search/SearchFiltering.java @@ -33,6 +33,7 @@ import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.List; @@ -206,7 +207,7 @@ public class SearchFiltering { */ public static class ArtifactTypeFilter extends AbstractFilter { - private final List types; + private final Collection types; /** * Construct a new ArtifactTypeFilter. @@ -214,7 +215,7 @@ public class SearchFiltering { * @param types The list of BlackboardArtifact types to include in * results from. */ - public ArtifactTypeFilter(List types) { + public ArtifactTypeFilter(Collection types) { this.types = types; } @@ -223,8 +224,8 @@ public class SearchFiltering { * * @return The list of artifact types specified by the filter. */ - public List getTypes() { - return Collections.unmodifiableList(types); + public Collection getTypes() { + return Collections.unmodifiableCollection(types); } private StringJoiner joinStandardArtifactTypes() { diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties-MERGED index 608ac005ae..4f5a801573 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties-MERGED @@ -72,6 +72,18 @@ MiniTimelineArtifactListPanel.value.noValue=No value available. MiniTimelineDateListPanel.countColumn.name=Count MiniTimelineDateListPanel.dateColumn.name=Date MiniTimelineDateListPanel.value.noValue=No value available. +MonthAbbreviation.aprilAbbrev=Apr +MonthAbbreviation.augustAbbrev=Aug +MonthAbbreviation.decemberAbbrev=Dec +MonthAbbreviation.feburaryAbbrev=Feb +MonthAbbreviation.januraryAbbrev=Jan +MonthAbbreviation.julyAbbrev=Jul +MonthAbbreviation.juneAbbrev=Jun +MonthAbbreviation.marchAbbrev=Mar +MonthAbbreviation.mayAbbrev=May +MonthAbbreviation.novemberAbbrev=Nov +MonthAbbreviation.octoberAbbrev=Oct +MonthAbbreviation.septemberAbbrev=Sep ObjectDetectedFilterPanel.error.text=At least one object type name must be selected. ParentFolderFilterPanel.error.text=At least one parent path must be entered. PastOccurrencesFilterPanel.error.text=At least one value in the past occurrence filter must be selected. diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryPanel.java index becc7b6511..ef3e4967f1 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainSummaryPanel.java @@ -22,15 +22,16 @@ import java.awt.Color; import java.awt.Component; import java.awt.Point; import java.awt.event.MouseEvent; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; +import java.time.Instant; +import java.time.ZonedDateTime; +import java.util.TimeZone; import javax.swing.ImageIcon; import javax.swing.JComponent; import javax.swing.JList; import javax.swing.ListCellRenderer; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.ThreadConfined; +import org.sleuthkit.autopsy.datamodel.ContentUtils; /** * Class which displays a preview and details about a domain. @@ -39,7 +40,6 @@ class DomainSummaryPanel extends javax.swing.JPanel implements ListCellRenderer< private static final long serialVersionUID = 1L; private static final Color SELECTION_COLOR = new Color(0, 120, 215); - private static final SimpleDateFormat dateFormat = new SimpleDateFormat("MMM dd yyyy", Locale.getDefault()); /** * Creates new form DomainPanel. @@ -149,8 +149,9 @@ class DomainSummaryPanel extends javax.swing.JPanel implements ListCellRenderer< @Override public Component getListCellRendererComponent(JList list, DomainWrapper value, int index, boolean isSelected, boolean cellHasFocus) { domainNameLabel.setText(value.getResultDomain().getDomain()); - String startDate = dateFormat.format(new Date(value.getResultDomain().getActivityStart() * 1000)); - String endDate = dateFormat.format(new Date(value.getResultDomain().getActivityEnd() * 1000)); + TimeZone timeZone = ContentUtils.getTimeZone(value.getResultDomain().getDataSource()); + String startDate = formatDate(value.getResultDomain().getActivityStart(), timeZone); + String endDate = formatDate(value.getResultDomain().getActivityEnd(), timeZone); activityLabel.setText(Bundle.DomainSummaryPanel_activity_text(startDate, endDate)); totalVisitsLabel.setText(Bundle.DomainSummaryPanel_totalPages_text() + value.getResultDomain().getTotalPageViews()); pagesLabel.setText(Bundle.DomainSummaryPanel_pages_text() + value.getResultDomain().getPageViewsInLast60Days()); @@ -165,6 +166,22 @@ class DomainSummaryPanel extends javax.swing.JPanel implements ListCellRenderer< setBackground(isSelected ? SELECTION_COLOR : list.getBackground()); return this; } + + /** + * Formats an epoch time in a given time zone using the following pattern + * + * MMM dd YYYY + * + * The pattern below is formatted manually to reuse the MonthAbbreviation utility. + */ + private String formatDate(long epochSeconds, TimeZone timeZone) { + Instant epochSecondsAsInstant = Instant.ofEpochSecond(epochSeconds); + ZonedDateTime dateTime = ZonedDateTime.ofInstant(epochSecondsAsInstant, timeZone.toZoneId()); + MonthAbbreviation currentCutOffMonth = MonthAbbreviation.fromMonthValue(dateTime.getMonthValue()); + return String.format("%s %02d %04d", + currentCutOffMonth.toString(), + dateTime.getDayOfMonth(), dateTime.getYear()); + } @ThreadConfined(type = ThreadConfined.ThreadType.AWT) @Override diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/MonthAbbreviation.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/MonthAbbreviation.java new file mode 100755 index 0000000000..0a9c896ac4 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/MonthAbbreviation.java @@ -0,0 +1,80 @@ +/* + * Autopsy + * + * Copyright 2020 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * 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 org.sleuthkit.autopsy.discovery.ui; + +import org.openide.util.NbBundle; + +/** + * Utility for representing month abbreviations + */ +@NbBundle.Messages({ + "MonthAbbreviation.januraryAbbrev=Jan", + "MonthAbbreviation.feburaryAbbrev=Feb", + "MonthAbbreviation.marchAbbrev=Mar", + "MonthAbbreviation.aprilAbbrev=Apr", + "MonthAbbreviation.mayAbbrev=May", + "MonthAbbreviation.juneAbbrev=Jun", + "MonthAbbreviation.julyAbbrev=Jul", + "MonthAbbreviation.augustAbbrev=Aug", + "MonthAbbreviation.septemberAbbrev=Sep", + "MonthAbbreviation.octoberAbbrev=Oct", + "MonthAbbreviation.novemberAbbrev=Nov", + "MonthAbbreviation.decemberAbbrev=Dec" +}) +public enum MonthAbbreviation { + JANURARY(Bundle.MonthAbbreviation_januraryAbbrev()), + FEBURARY(Bundle.MonthAbbreviation_feburaryAbbrev()), + MARCH(Bundle.MonthAbbreviation_marchAbbrev()), + APRIL(Bundle.MonthAbbreviation_aprilAbbrev()), + MAY(Bundle.MonthAbbreviation_mayAbbrev()), + JUNE(Bundle.MonthAbbreviation_juneAbbrev()), + JULY(Bundle.MonthAbbreviation_julyAbbrev()), + AUGUST(Bundle.MonthAbbreviation_augustAbbrev()), + SEPTEMBER(Bundle.MonthAbbreviation_septemberAbbrev()), + OCTOBER(Bundle.MonthAbbreviation_octoberAbbrev()), + NOVEMBER(Bundle.MonthAbbreviation_novemberAbbrev()), + DECEMBER(Bundle.MonthAbbreviation_decemberAbbrev()); + + private final String abbreviation; + + MonthAbbreviation(String abbreviation) { + this.abbreviation = abbreviation; + } + + @Override + public String toString() { + return this.abbreviation; + } + + /** + * Converts a month value (1-12) to the appropriate abbreviation. + * + * @param value Month value (1-12). + * @return Abbreviation matching the month value, null if not found. + */ + public static MonthAbbreviation fromMonthValue(int value) { + MonthAbbreviation[] months = MonthAbbreviation.values(); + for(int i = 0; i < months.length; i++) { + if (i + 1 == value) { + return months[i]; + } + } + return null; + } +}