%-----------------------------------------------------------------------------% % vim: ft=mercury ts=4 sw=4 et %-----------------------------------------------------------------------------% % Copyright (C) 2001-2002, 2004-2006, 2011 The University of Melbourne. % Copyright (C) 2014-2015, 2019-2021, 2024 The Mercury team. % This file may only be copied under the terms of the GNU General % Public License - see the file COPYING in the Mercury distribution. %-----------------------------------------------------------------------------% % % File: timestamp.m. % Main author: stayl. % % Timestamp representation for smart recompilation. % %-----------------------------------------------------------------------------% :- module libs.timestamp. :- interface. :- import_module time. % For use by predicates that do or do not return timestamps, as requested. :- type maybe_return_timestamp ---> do_not_return_timestamp ; do_return_timestamp. % For use by predicates that already have a record of what a file's % contents were at a particular point in time. :- type read_module_and_timestamps ---> always_read_module(maybe_return_timestamp) % Read the file regardless of its timestamp. ; do_not_read_module_if_match(timestamp). % If the file's timestamp matches the one given here, don't read % the file; just return its timestamp (which is this timestamp). % A `timestamp' is similar to a `time_t' except that timestamps are system % independent. A timestamp string (obtained using timestamp_to_string) % written on one system can be read on any other system. Comparison of % values of type `timestamp' (via compare/3) is equivalent to comparison % of the times represented. :- type timestamp. % Return a timestamp which is older than any other timestamp. % :- func oldest_timestamp = timestamp. % Return a timestamp which is newer than any other timestamp. % :- func newest_timestamp = timestamp. % Converts the calendar time value `Time' into a timestamp. % Equivalent to `gm_time_to_timestamp(gmtime(Time))'. % :- func time_t_to_timestamp(time_t) = timestamp. % Converts a timestamp into a string with format "yyyy-mm-dd hh:mm:ss", % expressed as UTC. % :- func timestamp_to_string(timestamp) = string. % Converts a string formatted as "yyyy-mm-dd hh:mm:ss", into a timestamp. % Fails if the string does not have the correct format. % :- pred parse_timestamp_string(string::in, timestamp::out) is semidet. %-----------------------------------------------------------------------------% %-----------------------------------------------------------------------------% :- implementation. :- import_module char. :- import_module int. :- import_module maybe. :- import_module string. %-----------------------------------------------------------------------------% % A timestamp is a string formatted as "yyyy-mm-dd hh:mm:ss" % representing a time expressed as UTC (Universal Coordinated Time). % % We use a no-tag type rather than an abstract equivalence type to avoid % type errors with abstract equivalence types in the hlc backend. :- type timestamp ---> timestamp(string). oldest_timestamp = timestamp("0000-00-00 00:00:00"). newest_timestamp = timestamp("9999-99-99 99:99:99"). time_t_to_timestamp(Time) = gmtime_to_timestamp(time.gmtime(Time)). :- func gmtime_to_timestamp(tm) = timestamp. gmtime_to_timestamp(tm(Year, Month, MD, Hrs, Min, Sec, YD, WD, DST)) = timestamp(gmtime_to_timestamp_string(Year, Month, MD, Hrs, Min, Sec, YD, WD, maybe_dst_to_int(DST))). :- func gmtime_to_timestamp_string(int, int, int, int, int, int, int, int, int) = string. :- pragma foreign_decl("C", " #include ""mercury_string.h"" #include "). :- pragma foreign_proc("C", gmtime_to_timestamp_string(Yr::in, Mnt::in, MD::in, Hrs::in, Min::in, Sec::in, YD::in, WD::in, N::in) = (Result::out), [will_not_call_mercury, promise_pure], "{ int size; struct tm t; t.tm_sec = (int) Sec; t.tm_min = (int) Min; t.tm_hour = (int) Hrs; t.tm_mon = (int) Mnt; t.tm_year = (int) Yr; t.tm_wday = (int) WD; t.tm_mday = (int) MD; t.tm_yday = (int) YD; t.tm_isdst = (int) N; size = sizeof ""yyyy-mm-dd hh:mm:ss""; MR_allocate_aligned_string_msg(Result, size - 1, MR_ALLOC_ID); strftime(Result, size, ""%Y-%m-%d %H:%M:%S"", &t); }"). :- pragma foreign_proc("C#", gmtime_to_timestamp_string(Yr::in, Mnt::in, MD::in, Hrs::in, Min::in, Sec::in, _YD::in, _WD::in, _N::in) = (Result::out), [will_not_call_mercury, promise_pure], "{ System.DateTime t; t = new System.DateTime(Yr + 1900, Mnt + 1, MD, Hrs, Min, Sec); string format_str = ""yyyy-MM-dd hh:mm:ss""; Result = t.ToString(format_str); }"). :- pragma foreign_proc("Java", gmtime_to_timestamp_string(Yr::in, Mnt::in, MD::in, Hrs::in, Min::in, Sec::in, _YD::in, _WD::in, _N::in) = (Result::out), [will_not_call_mercury, promise_pure], " java.util.GregorianCalendar cal = new java.util.GregorianCalendar(Yr + 1900, Mnt - 1, MD, Hrs, Min, Sec); java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat(""yyyy-MM-dd HH:mm:ss""); java.util.Date date = new java.util.Date(cal.getTimeInMillis()); Result = sdf.format(date); "). gmtime_to_timestamp_string(_, _, _, _, _, _, _, _, _) = _ :- sorry($file, $pred). :- func maybe_dst_to_int(maybe(dst)) = int. maybe_dst_to_int(M) = N :- ( M = yes(DST), ( DST = daylight_time, N = 1 ; DST = standard_time, N = 0 ) ; M = no, N = -1 ). timestamp_to_string(timestamp(Timestamp)) = Timestamp. parse_timestamp_string(Timestamp, timestamp(Timestamp)) :- % The if-then-else here is to force order of evaluation -- % we need to ensure that the sanity checks occur before the % calls to unsafe_index. The offsets are only valid if the string % contains only ASCII characters, as expected. ( if string.all_match(plausible_timestamp_char, Timestamp), string.length(Timestamp, TimestampLength), string.length("yyyy-mm-dd hh:mm:ss", ShouldBeTimestampLength), TimestampLength = ShouldBeTimestampLength then string.to_int(string.unsafe_between(Timestamp, 0, 4), _), string.unsafe_index(Timestamp, 4, '-'), string.to_int(string.unsafe_between(Timestamp, 5, 7), Month), Month >= 1, Month =< 12, string.unsafe_index(Timestamp, 7, '-'), string.to_int(string.unsafe_between(Timestamp, 8, 10), Day), Day >= 1, Day =< 31, string.unsafe_index(Timestamp, 10, ' '), string.to_int(string.unsafe_between(Timestamp, 11, 13), Hour), Hour >= 0, Hour =< 23, string.unsafe_index(Timestamp, 13, ':'), string.to_int(string.unsafe_between(Timestamp, 14, 16), Minute), Minute >= 0, Minute =< 59, string.unsafe_index(Timestamp, 16, ':'), string.to_int(string.unsafe_between(Timestamp, 17, 19), Second), Second >= 0, Second =< 61 % Seconds 60 and 61 are for leap seconds. else fail ). :- pred plausible_timestamp_char(char::in) is semidet. plausible_timestamp_char(Char) :- char.to_int(Char, CharInt), char.to_int(':', HighestInt), CharInt =< HighestInt. %-----------------------------------------------------------------------------% :- end_module libs.timestamp. %-----------------------------------------------------------------------------%