001/*
002 * Stallion Core: A Modern Web Framework
003 *
004 * Copyright (C) 2015 - 2016 Stallion Software LLC.
005 *
006 * This program is free software: you can redistribute it and/or modify it under the terms of the
007 * GNU General Public License as published by the Free Software Foundation, either version 2 of
008 * the License, or (at your option) any later version. This program is distributed in the hope that
009 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
011 * License for more details. You should have received a copy of the GNU General Public License
012 * along with this program.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>.
013 *
014 *
015 *
016 */
017
018package io.stallion.utils;
019
020import io.stallion.Context;
021import io.stallion.exceptions.UsageException;
022import io.stallion.settings.Settings;
023import io.stallion.utils.json.JSON;
024import org.parboiled.common.StringUtils;
025
026import java.time.Instant;
027import java.time.LocalDateTime;
028import java.time.ZoneId;
029import java.time.ZonedDateTime;
030import java.time.format.DateTimeFormatter;
031import java.util.Date;
032
033import static io.stallion.utils.Literals.empty;
034import static io.stallion.utils.Literals.emptyInstance;
035
036
037public class DateUtils {
038    static ZoneId UTC = ZoneId.of("UTC");
039
040    public static final DateTimeFormatter DEFAULT_FORMAT = DateTimeFormatter.ofPattern("MMM d, YYYY h:mm a");
041    public static final DateTimeFormatter SLUG_FORMAT = DateTimeFormatter.ofPattern("YYYY-MM-dd-HHmm-ssSS");
042    public static final DateTimeFormatter ISO_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'");
043    public static final DateTimeFormatter SQL_FORMAT =  DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
044    public static final DateTimeFormatter MINUTE_FORMAT =  DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
045
046    /**
047     * Gets the current time in UTC
048     * @return a ZonedDateTime with the current time in UTC
049     */
050    public static ZonedDateTime utcNow()
051    {
052        return ZonedDateTime.now(UTC);
053    }
054
055    /**
056     * Milliseconds since the epoch
057     * @return
058     */
059    public static long mils() {
060        return new Date().getTime();
061    }
062
063    /**
064     * Converts milliseconds since the unix epoch to a ZonedDateTime in UTC
065     * @param mils
066     * @return
067     */
068    public static ZonedDateTime milsToDateTime(long mils) {
069        return ZonedDateTime.ofInstant(Instant.ofEpochMilli(mils), UTC);
070    }
071
072
073    /**
074     * Formats a ZonedDateTime into a string with the given DateTimeFormatter pattern
075     * @param date
076     * @param formatPattern
077     * @return
078     */
079    public static String formatLocalDateFromZonedDate(ZonedDateTime date, String formatPattern) {
080        if (date == null) {
081            return "";
082        }
083
084        ZonedDateTime localDt = date.withZoneSameInstant(Context.getSettings().getTimeZoneId());
085
086        DateTimeFormatter formatter;
087        if (StringUtils.isEmpty(formatPattern)) {
088            formatter = DEFAULT_FORMAT;
089        } else if ("iso".equals(formatPattern.toLowerCase())) {
090            formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
091        } else {
092            formatter = DateTimeFormatter.ofPattern(formatPattern);
093        }
094        return localDt.format(formatter);
095    }
096
097    /**
098     * Turns the date into a format that has no spaces, colons, or any other invalid characters invalid for a file name
099     * The actual format used is: YYYY-MM-dd-HHmm-ssSS
100     * @param epochMillis
101     * @return
102     */
103    public static String slugifyDate(Long epochMillis) {
104        return slugifyDate(ZonedDateTime.ofInstant(Instant.ofEpochMilli(epochMillis), UTC));
105    }
106
107    /**
108     * Turns the date into a format that has no spaces, colons, or any other invalid characters invalid for a file name
109     * The actual format used is: YYYY-MM-dd-HHmm-ssSS
110     * @param date
111     * @return
112     */
113    public static String slugifyDate(ZonedDateTime date) {
114        return date.format(SLUG_FORMAT);
115    }
116
117    /**
118     * Gets the current time in the local time zone. The local time zone is first determined by the current user, if no
119     * user, then use the zone defined in settings, if no settings, use the zone of the server
120     * @return
121     */
122    public static ZonedDateTime localNow() {
123        return ZonedDateTime.now(Context.getSettings().getTimeZoneId());
124    }
125
126
127    public static ZoneId currentUserTimeZoneId() {
128        if (!empty(Context.getUser().getTimeZoneId())) {
129            return ZoneId.of(Context.getUser().getTimeZoneId());
130        } else if (!emptyInstance(Settings.instance().getTimeZoneId())) {
131            return Settings.instance().getTimeZoneId();
132        } else {
133            return ZoneId.of("UTC");
134        }
135    }
136
137    public static int currentUserUtcOffsetMinutes() {
138        ZonedDateTime now = ZonedDateTime.now(currentUserTimeZoneId());
139        return now.getOffset().getTotalSeconds() / 60;
140    }
141
142    /**
143     * Format the current time using a date format string
144     * @param format
145     * @return
146     */
147    public static String formatNow(String format) {
148        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
149        return utcNow().format(formatter);
150    }
151
152    /* We have to add this generic Object overload, because Method typing dispatching
153    * does not work correctly when called from the templates. So instead we have to
154    * include the type in the name of each function. Blech.
155    * */
156    public static String formatLocalDate(Object dt, String formatPattern) {
157        if (dt instanceof Long) {
158            return formatLocalDateFromLong((Long) dt, formatPattern);
159        } else if (dt instanceof ZonedDateTime) {
160            return formatLocalDateFromZonedDate((ZonedDateTime) dt, formatPattern);
161        } else if (dt instanceof Date) {
162            return formatLocalDateFromJDate((Date) dt, formatPattern);
163        } else if (dt instanceof String) {
164            return formatLocalDateFromString((String)dt, formatPattern);
165        }
166        return "";
167    }
168
169    public static String formatLocalDate(Object dt) {
170        return formatLocalDate(dt, null);
171    }
172
173
174    public static String formatLocalDateFromJDate(Date date) {
175        return formatLocalDateFromJDate(date, null);
176    }
177
178    public static String formatLocalDateFromJDate(Date date, String formatPattern) {
179        if (date == null) {
180            return "";
181        }
182        return formatLocalDateFromZonedDate(ZonedDateTime.ofInstant(date.toInstant(), UTC), formatPattern);
183    }
184
185    public static String formatLocalDateFromString(String dateStamp, String formatPattern) {
186        LocalDateTime ldt = null;
187        if (dateStamp.length() == 19) {
188             ldt = LocalDateTime.parse(dateStamp, SQL_FORMAT);
189        } else if (dateStamp.length() == 20) {
190            ldt = LocalDateTime.parse(dateStamp, SLUG_FORMAT);
191        }
192        if (ldt == null) {
193            throw new UsageException("DateStamp " + dateStamp + " did not have a recognizable format.");
194        }
195        return formatLocalDateFromZonedDate(ldt.atZone(UTC), formatPattern);
196    }
197
198    public static String formatLocalDateFromLong(long epochMillis) {
199        return formatLocalDateFromLong(epochMillis, null);
200    }
201
202    public static String formatLocalDateFromLong(long epochMillis, String formatPattern) {
203        if (epochMillis == 0L) {
204            return "";
205        }
206        return formatLocalDateFromZonedDate(ZonedDateTime.ofInstant(Instant.ofEpochMilli(epochMillis), UTC), formatPattern);
207    }
208
209    public static String formatLocalDateFromLong(Long epochMillis) {
210        return formatLocalDateFromLong(epochMillis, null);
211    }
212
213    public static String formatLocalDateFromLong(Long epochMillis, String formatPattern) {
214        if (epochMillis == 0L || epochMillis == null) {
215            return "";
216        }
217        return formatLocalDateFromZonedDate(ZonedDateTime.ofInstant(Instant.ofEpochMilli(epochMillis), UTC), formatPattern);
218    }
219
220    public static String formatLocalDateFromZonedDate(ZonedDateTime date) {
221        return formatLocalDateFromZonedDate(date, null);
222    }
223
224}