1/*
2 * Copyright 2012-2017 elementary, Inc. (https://elementary.io)
3 * SPDX-License-Identifier: LGPL-3.0-or-later
4 */
5
6/**
7 * The DateTime namespace contains useful functions for
8 * getting the default translated format for either date and time.
9 */
10namespace Granite.DateTime {
11    /**
12     * Gets a default translated time format.
13     * The function constructs a new string interpreting the //is_12h// and //with_second// parameters
14     * so that it can be used with formatting functions like {@link GLib.DateTime.format}.
15     *
16     * The returned string is formatted and translated. This function is mostly used to display
17     * the time in various user interfaces like the time displayed in the top panel.
18     *
19     * @param is_12h if the returned string should be formatted in 12h format
20     * @param with_second if the returned string should include seconds
21     *
22     * @return the formatted and located time string.
23     */
24    public static string get_default_time_format (bool is_12h = false, bool with_second = false) {
25        if (is_12h == true) {
26            if (with_second == true) {
27                /// TRANSLATORS: a GLib.DateTime format showing the hour (12h format) with seconds
28                return _("%-l:%M:%S %p");
29            } else {
30                /// TRANSLATORS: a GLib.DateTime format showing the hour (12h format)
31                return _("%-l:%M %p");
32            }
33        } else {
34            if (with_second == true) {
35                /// TRANSLATORS: a GLib.DateTime format showing the hour (24h format) with seconds
36                return _("%H:%M:%S");
37            } else {
38                /// TRANSLATORS: a GLib.DateTime format showing the hour (24h format)
39                return _("%H:%M");
40            }
41        }
42    }
43
44    /**
45     * Compares a {@link GLib.DateTime} to {@link GLib.DateTime.now_local} and returns a location, relative date and
46     * time string. Results appear as natural-language strings like "Now", "5m ago", "Yesterday"
47     *
48     * @param date_time a {@link GLib.DateTime} to compare against {@link GLib.DateTime.now_local}
49     *
50     * @return a localized, relative date and time string
51     */
52    public static string get_relative_datetime (GLib.DateTime date_time) {
53        var now = new GLib.DateTime.now_local ();
54        var diff = now.difference (date_time);
55
56        if (is_same_day (date_time, now)) {
57            if (diff > 0) {
58                if (diff < TimeSpan.MINUTE) {
59                    return _("Now");
60                } else if (diff < TimeSpan.HOUR) {
61                    var minutes = diff / TimeSpan.MINUTE;
62                    return dngettext (GETTEXT_PACKAGE, "%dm ago", "%dm ago", (ulong) (minutes)).printf ((int) (minutes));
63                } else if (diff < 12 * TimeSpan.HOUR) {
64                    int rounded = (int) Math.round ((double) diff / TimeSpan.HOUR);
65                    return dngettext (GETTEXT_PACKAGE, "%dh ago", "%dh ago", (ulong) rounded).printf (rounded);
66                }
67            } else {
68                diff = -1 * diff;
69                if (diff < TimeSpan.HOUR) {
70                    var minutes = diff / TimeSpan.MINUTE;
71                    return dngettext (GETTEXT_PACKAGE, "in %dm", "in %dm", (ulong) (minutes)).printf ((int) (minutes));
72                } else if (diff < 12 * TimeSpan.HOUR) {
73                    int rounded = (int) Math.round ((double) diff / TimeSpan.HOUR);
74                    return dngettext (GETTEXT_PACKAGE, "in %dh", "in %dh", (ulong) rounded).printf (rounded);
75                }
76            }
77
78            return date_time.format (get_default_time_format (is_clock_format_12h (), false));
79        } else if (is_same_day (date_time.add_days (1), now)) {
80            return _("Yesterday");
81        } else if (is_same_day (date_time.add_days (-1), now)) {
82            return _("Tomorrow");
83        } else if (diff < 6 * TimeSpan.DAY && diff > -6 * TimeSpan.DAY) {
84            return date_time.format (get_default_date_format (true, false, false));
85        } else if (date_time.get_year () == now.get_year ()) {
86            return date_time.format (get_default_date_format (false, true, false));
87        } else {
88            return date_time.format ("%x");
89        }
90    }
91
92    /**
93     * Gets the //clock-format// key from //org.gnome.desktop.interface// schema
94     * and determines if the clock format is 12h based
95     *
96     * @return true if the clock format is 12h based, false otherwise.
97     */
98    private static bool is_clock_format_12h () {
99        string format = null;
100        try {
101            var portal = Portal.Settings.get ();
102            var variant = portal.read ("org.gnome.desktop.interface", "clock-format").get_variant ();
103            format = variant.get_string ();
104        } catch (Error e) {
105            debug ("cannot use portal, using GSettings: %s", e.message);
106        }
107
108        if (format == null) {
109            var h24_settings = new GLib.Settings ("org.gnome.desktop.interface");
110            format = h24_settings.get_string ("clock-format");
111        }
112
113        return (format.contains ("12h"));
114    }
115
116    /**
117     * Compare two {@link GLib.DateTime} and return true if they occur on the same day of the same year
118     *
119     * @param day1 a {@link GLib.DateTime} to compare against day2
120     * @param day2 a {@link GLib.DateTime} to compare against day1
121     *
122     * @return true if day1 and day2 occur on the same day of the same year. False otherwise
123     */
124    public static bool is_same_day (GLib.DateTime day1, GLib.DateTime day2) {
125        return day1.get_day_of_year () == day2.get_day_of_year () && day1.get_year () == day2.get_year ();
126    }
127
128    /**
129     * Gets the default translated date format.
130     * The function constructs a new string interpreting the //with_weekday//, //with_day// and //with_year// parameters
131     * so that it can be used with formatting functions like {@link GLib.DateTime.format}.
132     *
133     * As the {@link Granite.DateTime.get_default_time_format}, the returned string is formatted, translated and is also mostly used to display
134     * the date in various user interfaces like the date displayed in the top panel.
135     *
136     * @param with_weekday if the returned string should contain the abbreviated weekday name
137     * @param with_day if the returned string should contain contain the day of the month as a decimal number (range 1 to 31)
138     * @param with_year if the returned string should contain the year as a decimal number including the century
139     *
140     * @return returns the formatted and located date string. If for some reason, the function could not determine the format to use,
141     *         an empty string will be returned.
142     */
143    public static string get_default_date_format (bool with_weekday = false, bool with_day = true, bool with_year = false) {
144        if (with_weekday == true && with_day == true && with_year == true) {
145            /// TRANSLATORS: a GLib.DateTime format showing the weekday, date, and year
146            return _("%a, %b %e, %Y");
147        } else if (with_weekday == false && with_day == true && with_year == true) {
148            /// TRANSLATORS: a GLib.DateTime format showing the date and year
149            return _("%b %e %Y");
150        } else if (with_weekday == false && with_day == false && with_year == true) {
151            /// TRANSLATORS: a GLib.DateTime format showing the year
152            return _("%Y");
153        } else if (with_weekday == false && with_day == true && with_year == false) {
154            /// TRANSLATORS: a GLib.DateTime format showing the date
155            return _("%b %e");
156        } else if (with_weekday == true && with_day == false && with_year == true) {
157            /// TRANSLATORS: a GLib.DateTime format showing the weekday and year.
158            return _("%a %Y");
159        } else if (with_weekday == true && with_day == false && with_year == false) {
160            /// TRANSLATORS: a GLib.DateTime format showing the weekday
161            return _("%a");
162        } else if (with_weekday == true && with_day == true && with_year == false) {
163            /// TRANSLATORS: a GLib.DateTime format showing the weekday and date
164            return _("%a, %b %e");
165        } else if (with_weekday == false && with_day == false && with_year == false) {
166            /// TRANSLATORS: a GLib.DateTime format showing the month.
167            return _("%b");
168        }
169
170        return "";
171    }
172
173    /**
174     * Converts seconds into the ISO 8601 standard date format for minutes (e.g. 100s to 01:40).
175     * Output of negative seconds is prepended with minus character.
176     *
177     * @param seconds the number of seconds to convert into ISO 8601
178     *
179     * @return returns an ISO 8601 formatted string
180     */
181    public static string seconds_to_time (int seconds) {
182        int sign = 1;
183        if (seconds < 0) {
184            seconds = -seconds;
185            sign = -1;
186        }
187
188        int hours = seconds / 3600;
189        int min = (seconds % 3600) / 60;
190        int sec = (seconds % 60);
191
192        if (hours > 0) {
193            return ("%d:%02d:%02d".printf (sign * hours, min, sec));
194        } else {
195            return ("%02d:%02d".printf (sign * min, sec));
196        }
197    }
198}
199