I am trying to use the Duration class instead of long.
It has superior literal syntax. I like its flexibility, though it looks weird.
"PT10S" means 10 seconds, what is the problem to accept "10 seconds"?!
Okay never mind.
I am just curious why PT prefix has been chosen (not "DU" e.g.) and why any prefix is better here rather than nothing?
As can be found on the page Jesper linked to (ISO-8601 - Data elements and interchange formats – Information interchange – Representation of dates and times)
P is the duration designator (for period) placed at the start of the duration representation.
Y is the year designator that follows the value for the number of years.
M is the month designator that follows the value for the number of months.
W is the week designator that follows the value for the number of weeks.
D is the day designator that follows the value for the number of days.
T is the time designator that precedes the time components of the representation.
So P means 'Period' and because there are no date-components it only has a 'Time'.
You could interpret this as 'Period of Time'
The 'why' this was chosen, you have to ask the ISO members that wrote the standard, but my guess is that it is easier to parse. (short and unambigious)
The details for the time component are:
H is the hour designator that follows the value for the number of hours.
M is the minute designator that follows the value for the number of minutes.
S is the second designator that follows the value for the number of seconds.
The value of PT20S then parses to:
Period
Time
20
Seconds
So, a duration of 20 seconds.
More examples can be found in the javadoc: https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html#parse-java.lang.CharSequence-
Java has taken a subset of the ISO 8601 standard format for a duration. So the “why” is why the standard was written the way it is, and it’s a guessing game. My go is:
P for period was chosen so that you can distinguish a duration from a date and/or time. Especially since a period may also be written in the same format as a local date-time, for example P0003-06-04T12:30:05 for 3 years 6 months 4 days 12 hours 30 minutes 5 seconds, the P can be necessary to distinguish. The P also gives a little but quick and convenient bit of validation in case you happen to pass a completely different string in a place where a duration was expected. And yes, PT10S looks weird, but once you get accustomed to it, you recognize it immediately as a duration, which can be practical.
T for time between the date part and the time part was chosen for two reasons:
For consistency with date-time strings that have T in the same place, for example 2018-07-04T15:00 for July 4, 2018 at 15:00 hours.
To disambiguate the otherwise ambiguous M for either months or minutes: P3M unambiguously means 3 months while PT3M means 3 minutes.
Actually if go on Duration API developed in Java since 1.8, they have gone with standard ISO 8601:
with java doc as below :
/**
* Applies an ISO 8601 Duration to a {#link ZonedDateTime}.
*
* <p>Since the JDK defined different types for the different parts of a Duration
* specification, this utility method is needed when a full Duration is to be applied to a
* {#link ZonedDateTime}. See {#link Period} and {#link Duration}.
*
* <p>All date-based parts of a Duration specification (Year, Month, Day or Week) are parsed
* using {#link Period#parse(CharSequence)} and added to the time. The remaining parts (Hour,
* Minute, Second) are parsed using {#link Duration#parse(CharSequence)} and added to the time.
*
* #param time A zoned date time to apply the offset to
* #param offset The offset in ISO 8601 Duration format
* #return A zoned date time with the offset applied
*/
public static ZonedDateTime addOffset(ZonedDateTime time, String offset) { }
Obtains a Duration from a text string of pattern: PnDTnHnMn.nS, where
nD = number of days,
nH = number of hours,
nM = number of minutes,
n.nS = number of seconds, the decimal point may be either a dot or a comma.
T = must be used before the part consisting of nH, nM, n.nS
Example of implementation with java as
import java.time.Duration;
public class ParseExample {
public static void main(String... args) {
parse("PT20S");//T must be at the beginning to time part
parse("P2D");//2 day
parse("-P2D");//minus 2 days
parse("P-2DT-20S");//S for seconds
parse("PT20H");//H for hours
parse("PT220H");
parse("PT20M");//M for minutes
parse("PT20.3S");//second can be in fraction like 20.3
parse("P4DT12H20M20.3S");
parse("P-4DT-12H-20M-20.3S");
parse("-P4DT12H20M20.3S");
}
private static void parse(String pattern) {
Duration d = Duration.parse(pattern);
System.out.println("Pattern: %s => %s%n", pattern, d);
}
}
I'm requesting the user to enter two dates (day, month and year).
Using the time.h header, I'm storing it in a struct tm variable like following (let's say date1):
date1.tm_mday = day;
date1.tm_mon = month;
date1.tm_year = year - 1900;
date1.tm_hour = 0;
date1.tm_min = 0;
date1.tm_sec = 0;
date1.tm_isdst = 0;
The second date (date2) is the same above but with "date2" instead of "date1" (two structures).
Next, I did the mktime of date1 and date2 to calculate the seconds of both dates:
date1TimeT = mktime(&date1);
date2TimeT = mktime(&date2);
Following, I checked whether the date1 is bigger than date2, so I placed:
if(date1TimeT > date2TimeT)
finalDateTimeT = date1TimeT - date2TimeT;
else
finalDateTimeT = date2TimeT - date1TimeT;
Now to calculate the days, months and years that have passed through this two dates:
totalDays = (int) finalDateTimeT / 86400;
remainingYears = totalDays / 365;
remainingMonths = (totalDays - remainingYears * 365) / 30;
remainingDays = (totalDays - remainingYears * 365 - remainingMonths * 30);
However, this is not 100% accurate, as this does not take care of the leap years (those who are divisible by 4).
Is there a safer way to calculate this? Keeping in mind the leap years.
How do I calculate the difference in days, months and years between two dates?
There are two totally different ways to do this. The more popular one is the way you've already tried: First convert both dates to some common, monotonic timebase, such as a Unix-style time_t value. Then subtract. This gives you a simple difference in, in this case, seconds. You can divide by 86400 to get days, but it's hard to see how to convert to months or years.
The reason it's hard is that our familiar year/month/day/hour/minute/second system of date/timekeeping is a mixed base scheme, and it's worse than that, in that different months have different numbers of days. There's no single constant days-per-month value you can divide by, the way there is for, say, inches-per-foot (or even hours-per-day).
And then time zones, daylight saving time, leap years, and leap seconds can make things even more complicated.
But in fact, those complications are one reason that the convert-to-monotonic-timebase technique is so popular, because it cuts through most of that complexity rather nicely. Assuming that library functions like mktime() and localtime() have been implemented properly, they take care of all the nasty details of time zones, daylight saving time, and leap years. Just about the only thing left for you to do is to make sure you use them correctly.
Using them correctly involves:
making sure to encode the tm_year and tm_mon fields of struct tm correctly (this is easy to get wrong, and in fact your posted code makes one of the common mistakes, forgetting that tm_mon is 0-based)
making sure to set tm_isdst to -1 if you want mktime to figure out whether DST applied for the date/time in question or not
setting tm_hour to 12 (not 0) if you're only interested in dates, not times (this helps to avoid certain anomalies that can crop up near DST transitions, although this probably wouldn't matter for your problem), and
when converting to days, remembering to round, not truncate, when dividing by 86400, since you may see an effective 23- or 25-hour day length if you're straddling a DST transition.
Leap years are generally not a problem when using this technique, and I'm not sure what you meant when you said that it was "not 100% accurate" because it "does not take care of the leap years". mktime and localtime understand leap years perfectly, and will allow you to compute a proper difference (in seconds) between two dates which straddle the February/March transition, in both leap and non-leap years. It's true that you won't get an accurate number of years if you just divide by the constant 365, but that's no worse than the way you'll often get the wrong number of months if you divide by a constant 30.
So if you've converted to a monotonic number of seconds, the answer to the question of how you convert to years, months, and days is: you don't. It's an impossible, ill-defined problem. You'll have to settle for presenting an answer in terms of just days, or perhaps divide by 7 to get weeks and days. (Or you can present a decidedly approximate answer after dividing by an average number of days per month, like 30.43.)
Now, just about everyone would agree that, informally at least, the difference between, say, February 5 and March 7 is "one month and two days". Notice that this is equally true in leap and non-leap years. If you want to implement this more "informal" or "human-centric" algorithm in a C program, you certainly can, although it's not going to involve mktime or localtime. Instead, it's going to be an implementation of mixed-base arithmetic, with a complication due to the fact that the number of days per month is not constant.
Suppose we want to find the difference between April 5, 2021 and June 10, 2022. That will look like
year month day
2022 6 10
- 2021 4 5
---- -- --
1 2 5
The answer is "One year, two months, and five days". This problem was simple, because we didn't have to borrow.
Here's a harder one: what's the difference between January 25,
2020 and March 5, 2020? That looks like
year month day
2020 3 5
- 2020 1 25
---- -- --
???? ?? ??
What are we supposed to do with this? The first date is clearly greater than the second, but of course 5 is less than 20. Informally, from January 25 to February 25 is one month, and then it's a few more days until March 5, but that "few more days" hits the worst case, because it straddles the end of February, so we have to worry about whether it's a leap year or not.
The way to work the problem is probably like this:
year month day
2020 2 34
- 2020 1 25
---- -- --
0 1 9
Here I have borrowed 1 from the month's column, and added it in to the day's column. Since 2020 was a leap year, there were 29 days in February, so the adjusted days number after the borrow is 5+29=34.
You might have to implement a similar borrow from the year's to the month's column, although that's easier, because the number of months per year is always 12. (That is, I'm pretty sure you will not have to worry about leap years at that point in the algorithm.)
I'm out of time for this too-long answer, so I'm not going to write any C code to do this mixed-base arithmetic, but with this prose outline I think you should be able to implement it straightforwardly enough.
Footnote 1: The algorithm I've presented isn't quite complete. It will not handle, for example, the interval from January 31 to March 1 properly. It's not necessarily obvious how to handle that rather problematic case, but the answer is probably to add the possibility of looping and borrowing twice, as #chux shows in his answer (see comment "Might loop twice").
Footnote 2: Remember that in our Gregorian calendar, the leap-year determination is not a simple test for divisibility by 4.
Footnote 3: For completeness and for anyone who might be curious, I'll say a little more about leap seconds. Unlike leap years, leap seconds are not just automatically taken care of for you by mktime and localtime. In fact, handling leap seconds properly is more or less impossible under the conventional mktime/localtime scheme, for the simple reason that the Unix time_t representation denies the existence of leap seconds altogether. Due to the presence of leap seconds, UTC is not the uniform, continuous, monotonic time scale that time_t assumes. Handling leap seconds properly requires thinking pretty carefully about how you want them to affect your addition and subtraction algorithms, and it requires using some data type other than time_t.
Footnote 4. Best not to worry at all about the anomalies that happened in 1752 (or whenever your country changed over to the Gregorian calendar). :-)
The idea to go through the delta days is a bad one. Once you pass the month boundary you are in trouble. Each month has 28..31 days. In your original post you assume every month to have 30 days.
In order not to loose relationship with the month's lengths we have to work directly with day/month/year. We can combine month+year in one variable as every year has 12 months, like the expression month+year*12. Here my code:
#include <time.h>
#include <stdio.h>
void PrintDelta (int StartYear, int StartMonth, int StartDay, int EndYear, int EndMonth, int EndDay)
{
//Month and Day are 1-based
int Start_MonthYear = StartYear * 12 + StartMonth-1;
int End_MonthYear = EndYear * 12 + EndMonth-1;
int Delta_MonthYear = End_MonthYear-Start_MonthYear;
int Delta_Days = EndDay-StartDay;
if (Delta_Days < 0)
{
//Like Jan 31st --> Feb 1st
//remove one month from Delta_MonthYear and add one month to Delta_Days
if (0)
{ //old buggy code used month before end date
static const int MonthDays[2][12]=
{
//MonthDays[][0] is the number of days in the month BEFORE January!!
//12 01 02 03 04 05 06 07 08 09 10 11 <--- month
{ 31,31,28,31,30,31,30,31,31,30,31,30}, //non-leap year
{ 31,31,29,31,30,31,30,31,31,30,31,30} //leap year
};
Delta_Days += MonthDays[(EndYear & 3) ? 0 : 1][EndMonth-1];
}
else
{ //better code uses month from start date
static const int MonthDays[2][12]=
{
{ 31,28,31,30,31,30,31,31,30,31,30,31}, //non-leap year
{ 31,29,31,30,31,30,31,31,30,31,30,31} //leap year
};
Delta_Days += MonthDays[(StartYear & 3) ? 0 : 1][StartMonth-1];
}
Delta_MonthYear--;
}
printf("Timespan from %d-%02d-%02d to %d-%02d-%02d is %d year(s), %d month(s), %d day(s)\n",
StartYear, StartMonth, StartDay,
EndYear, EndMonth, EndDay,
Delta_MonthYear/12, Delta_MonthYear%12, Delta_Days
);
}
int main(int argc, char **argv)
{
PrintDelta(2000,2,28, 2001,3,1); //1yr 1day, including Feb 29th
PrintDelta(2001,2,28, 2002,3,1); //1yr 1day, no Feb 29th
PrintDelta(2022,1,11, 2022,1,21); //10 days
PrintDelta(2022,1,31, 2022,2,1); //one day
PrintDelta(2022,1,31, 2022,3,1); //1 month 1 day
}
Subtract days, then months, then years. The only challenging issue is how to borrow from months
Some illustrative sample code below -simplifications exist.
#include <stdio.h>
int is_leap_year(int year) {
// Leave this for OP
}
// year: Gregorian calender
// mont: 0 = Dec, 1 = Jan, ... 12 = Dec
static int days_per_month(int year, int month) {
static const char dpm[] = {31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (month == 2 && is_leap_year(year)) {
return 29;
}
return dpm[month];
}
typedef struct {
int year, month, day;
} ymd_type;
// *a - *b
// Assume: a and b elements are already in their preferred ranges and a >= b.
ymd_type date_subtract(const struct tm *a, const struct tm *b) {
ymd_type ymd = {a->tm_year + 1900, a->tm_mon + 1, a->tm_mday};
ymd.day -= b->tm_mday;
while (ymd.day < 0) { // Might loop twice
ymd.month--;
ymd.day += days_per_month(ymd.year, ymd.month);
}
ymd.month -= b->tm_mon + 1;
if (ymd.month < 0) {
ymd.year--;
ymd.month += 12; // 12 months per year
}
ymd.year -= b->tm_year + 1900;
return ymd;
}
int test_ymd(int y1, int m1, int d1, int y0, int m0, int d0) {
const struct tm t1 = {.tm_year = y1 - 1900, .tm_mon = m1 - 1, .tm_mday = d1};
const struct tm t0 = {.tm_year = y0 - 1900, .tm_mon = m0 - 1, .tm_mday = d0};
ymd_type ymd = date_subtract(&t1, &t0);
printf(" %4d/%2d/%2d - %4d/%2d/%2d = %2dy %2dm %2dd\n", y1, m1, d1, y0, m0,
d0, ymd.year, ymd.month, ymd.day);
return 0;
}
Sample
int main() {
test_ymd(2022, 2, 17, 2020, 1, 1);
test_ymd(2022, 3, 1, 2020, 12, 31);
test_ymd(2022, 3, 1, 2022, 1, 31);
test_ymd(2020, 3, 1, 2020, 1, 31);
return 0;
}
Output
2022/ 2/17 - 2020/ 1/ 1 = 2y 1m 16d
2022/ 3/ 1 - 2020/12/31 = 1y 1m 29d
2022/ 3/ 1 - 2022/ 1/31 = 0y 0m 29d
2020/ 3/ 1 - 2020/ 1/31 = 0y 0m 30d
To Do:
Handle cases when dates are in the opposite order.
Handle cases when the date is not in the primary rage.
Handle extreme case like when year is near INT_MAX.
Im trying to get the current localtime in C, lets say Italy, I tried the code below but it returns a time three hours earlier than the real one.
For example if executed at 17:34 it will return 14:34, what am I doing wrong?
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
setenv("TZ", "UTC+1", 1); // Italy is UTC+1
tzset();
time_t sec = 1634224877; // Just for test
struct tm * end = localtime(&sec);
// Check for daylight save
if(end->tm_isdst == 1)
{
end->tm_hour += 1;
}
printf("Daylight save time?: %d\n", end->tm_isdst);
printf("Time is says: %.2d/%.2d/%d - %.2d:%.2d:%.2d\n", end->tm_mday, end->tm_mon + 1, end->tm_year + 1900,
end->tm_hour, end->tm_min, end->tm_sec);
}
Thanks
tl;dr If you want the time at a location, you have to set TZ to a location.
1634224877 is 2021-10-14 15:21:17 UTC. setenv takes POSIX time zones which don't work like you think. UTC+1 means one hour head of UTC. That is 14:21:17. If you want "UTC+1" you actually ask for UTC-1, one hour behind UTC.
But don't ask for UTC-1.
setenv("TZ", "UTC+1", 1); // Italy is UTC+1
That comment is not correct. Italy is not UTC+1. Sometimes Italy is UTC+1, sometimes it is UTC+2. In order to know about daylight savings time (and other wacky time zone issues), TZ needs to know your location.
Set TZ to the city closest to you, like Europe/Rome. Now localtime can figure out whether it's daylight savings time or not, you do not need to correct it.
int main()
{
setenv("TZ", "Europe/Rome", 1);
tzset();
time_t sec = 1634224877; // Just for test
struct tm * end = localtime(&sec);
printf("Daylight savings time?: %d\n", end->tm_isdst);
printf("Time is says: %.2d/%.2d/%d - %.2d:%.2d:%.2d\n", end->tm_mday, end->tm_mon + 1, end->tm_year + 1900,
end->tm_hour, end->tm_min, end->tm_sec);
}
Daylight savings time?: 1
Time is says: 14/10/2021 - 17:21:17
The system that manages time zones is called tzdata. It is a database of locations, their time zone information, daylight savings time switches, and a host of other wacky time zone information. It's what lets your computer know that Rome is usually UTC+1 but should sometimes be UTC+2.
A list of all tzdata locations can be had on Wikipedia, but these do not necessarily match the tzdata installed on your machine.
The TZ environment variable value FOO+1 (I changed it from UTC+1 to avoid confusion) is interpreted as a standard time zone designated "FOO" with no alternate (daylight savings) zone. The +1 (the + is optional) means that 1 hour needs to be added to the local time to convert it to Coordinated Universal Time (UTC). To specify an alternate (daylight savings) time zone, it is added after the standard time offset, e.g. FOO+1BAR0. The offset after the alternate zone can be omitted in which case it defaults to one less than the standard offset, so FOO+1BAR0 can be shortened to FOO1BAR. This means that local time will be 1 hour behind UTC when standard time is in effect and will be UTC when alternate (daylight savings) time is in effect. Optionally, the offset can include minutes or minutes and seconds, e.g. FOO+01:00BAR+00:00:00.
Italy uses Central European Time (CET, corresponding to UTC+1) when standard time is in effect (e.g. during winter), and uses Central European Summer Time (CEST, corresponding to UTC+1) when alternate (daylight savings) time is in effect (e.g. during summer). That can be expressed by the TZ environment variable value CET-1CEST-2 or CET-1CEST. Notice that the offsets used in the TZ environment variable have the opposite sign to the usual convention.
When TZ has one of the previously mentioned values with an alternate time (e.g. CET-1CEST), it is left up to the system libraries to use some arbitrary (and usually incorrect for most of the world) rule to determine the date and time of transitions between standard time and alternate time. Simple rules for the date and time of exactly two transitions per year can be encoded after the alternate zone designation and offset in the TZ variable, separated by commas. The transition date can be specified as Mm.n.d, meaning the dth day (0 = Sunday, 1 = Monday, ..., 6 = Saturday) of the nth week (counting from 1) of the mth month (1 = January, ..., 12 = December). n = 5 is interpreted as the last d day of the month m. The transition date can also be specified as Jn where n is the day of the year, not counting February 29 (so March 1 is always day 60). The (optional) transition time can be specified as /time, where time specifies the current local time on the transition date at which the transition to the other time is made, defaulting to 02:00:00. The time value can be abbreviated in the same way as time zone offsets, so /2 is the same as /02:00:00, but no leading plus or minus sign is allowed (by the current standard).
Italy currently operates on EU time zone transition rules (the abolition of which is currently postponed), where the transitions occur at 01:00 UTC and local time advances 1 hour on the last Sunday of March (M3.5.0) and retreats 1 hour on the last Sunday of October (M10.5.0). For Italy, the local transition times are 02:00:00 in March (advancing to 03:00:00) and 03:00:00 in October (retreating to 02:00:00), so the rules are M3.5.0/2 and M10.5.3. (M3.5.0/2 can be shortened to M3.5.0 since it uses the default transition time.)
The following, modified code will work to show time in Italy, at least until the current EU time zone rules are abolished:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
setenv("TZ", "CET-1CEST,M3.5.0,M10.5.0/3", 1); // Italy rules!
tzset();
time_t sec;
#if 0
sec = 1634224877; // Just for test
#else
sec = time(NULL); // For current time
#endif
struct tm * end = localtime(&sec);
printf("Daylight save time?: %d\n", end->tm_isdst);
printf("Time is says: %.2d/%.2d/%d - %.2d:%.2d:%.2d\n",
end->tm_mday, end->tm_mon + 1, end->tm_year + 1900,
end->tm_hour, end->tm_min, end->tm_sec);
}
I have a Unix timestamp that I need to get the individual year, month, day, hour, minute and second values from. I never was very good in math class so I was wondering if you guys could help me out a little :)
I have to do everything myself (no time.h functions). The language is C.
Disclaimer: The following code does not account for leap years or leap seconds [Unix time does not account for leap seconds. They're overrated, anyway. -Ed]. Also, I did not test it, so there may be bugs. It may kick your cat and insult your mother. Have a nice day.
Let's try a little psuedocode (Python, really):
# Define some constants here...
# You'll have to figure these out. Don't forget about February (leap years)...
secondsPerMonth = [ SECONDS_IN_JANUARY, SECONDS_IN_FEBRUARY, ... ]
def formatTime(secondsSinceEpoch):
# / is integer division in this case.
# Account for leap years when you get around to it :)
year = 1970 + secondsSinceEpoch / SECONDS_IN_YEAR
acc = secondsSinceEpoch - year * SECONDS_IN_YEAR
for month in range(12):
if secondsPerMonth[month] < acc:
acc -= month
month += 1
month += 1
# Again, / is integer division.
days = acc / SECONDS_PER_DAY
acc -= days * SECONDS_PER_DAY
hours = acc / SECONDS_PER_HOUR
acc -= hours * SECONDS_PER_HOUR
minutes = acc / SECONDS_PER_MINUTE
acc -= minutes * SECONDS_PER_MINUTE
seconds = acc
return "%d-%d-%d %d:%d%d" % (year, month, day, hours, minutes, seconds)
If I goofed up, please let me know. Doing this in C shouldn't be too much harder.
You really do not want to do this by hand. You could write up some simple code that assumes years, months, days, hours, minutes and seconds are all the same lengths (12 months; 28, 30, or 31 days; 24 hours; 60 minutes; and 60 seconds) and come up with the wrong answer.
To get the right answer, you have to handle leap years and leap seconds, and convert to the local time zone (with the right DST mode). (Unless you choose to only display in UTC time.)
I suggest that you have a look at the code of glibc and see how strftime works.
Edit: UNIX time does not use the leap second.
That format is often called ISO 8601, and searching for that might help you out.
You should have done a search - I posted a complete answer to this question over here just the other day (for UTC, at least - to adjust for other timezones just add or subtract the timezone offset in seconds from the unixtime before you call the function).
You don't have to do math. This can be easily handled in C like this,
char *ISO_Time (
time_t time
)
{
static char mStr[128];
struct tm *gmt;
gmt = gmtime (&time);
strftime (mStr, sizeof(mStr), "%Y-%m-%dT%H:%M:%SZ", gmt);
return mStr;
}
Since yours is not exactly ISO time, you just need to change one line,
strftime (mStr, sizeof(mStr), "%Y-%m-%d %H:%M:%S", gmt);